Plugin.NFC added to rpoject

This commit is contained in:
2024-12-17 17:01:50 +01:00
parent 40e651e30a
commit a605bb9947
12 changed files with 3270 additions and 0 deletions
@@ -0,0 +1,90 @@
using Android.App;
using Android.Content;
using Android.OS;
using System;
namespace Plugin.NFC
{
/// <summary>
/// Cross NFC (Android specific)
/// </summary>
public static partial class CrossNFC
{
internal static ActivityLifecycleContextListener lifecycleListener;
/// <summary>
/// Initialization
/// </summary>
/// <param name="application">Android <see cref="Application"/></param>
public static void Init(Application application)
{
if (lifecycleListener != null) return;
lifecycleListener = new ActivityLifecycleContextListener();
application.RegisterActivityLifecycleCallbacks(lifecycleListener);
}
/// <summary>
/// Initialization
/// </summary>
/// <param name="activity">Android <see cref="Activity"/></param>
public static void Init(Activity activity)
{
Init(activity.Application);
lifecycleListener.Activity = activity;
}
/// <summary>
/// Overrides Activity.OnNewIntent()
/// </summary>
/// <param name="intent">Android <see cref="Intent"/></param>
public static void OnNewIntent(Intent intent) => ((NFCImplementation)Current).HandleNewIntent(intent);
/// <summary>
/// Overrides Activity.OnResume()
/// </summary>
public static void OnResume() => ((NFCImplementation)Current).HandleOnResume();
/// <summary>
/// Returns the current Android <see cref="Context"/>
/// </summary>
internal static Context AppContext => Application.Context;
/// <summary>
/// Returns the current Android <see cref="Activity"/>
/// </summary>
/// <param name="throwError"></param>
/// <returns></returns>
internal static Activity GetCurrentActivity(bool throwError)
{
var activity = lifecycleListener?.Activity;
if (throwError && activity == null)
throw new NullReferenceException("The current Activity can not be detected. Ensure that you have called Init in your Activity or Application class.");
return activity;
}
}
/// <summary>
/// James Montemagno's ActivityLifecycleContextListener from CurrentActivityPlugin
/// <see href="https://github.com/jamesmontemagno/CurrentActivityPlugin"/>
/// </summary>
class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks
{
WeakReference<Activity> _currentActivity = new WeakReference<Activity>(null);
internal Context Context => Activity ?? Application.Context;
internal Activity Activity
{
get => _currentActivity.TryGetTarget(out var a) ? a : null;
set => _currentActivity.SetTarget(value);
}
void Application.IActivityLifecycleCallbacks.OnActivityCreated(Activity activity, Bundle savedInstanceState) => Activity = activity;
void Application.IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity) { }
void Application.IActivityLifecycleCallbacks.OnActivityPaused(Activity activity) => Activity = activity;
void Application.IActivityLifecycleCallbacks.OnActivityResumed(Activity activity) => Activity = activity;
void Application.IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState) { }
void Application.IActivityLifecycleCallbacks.OnActivityStarted(Activity activity) { }
void Application.IActivityLifecycleCallbacks.OnActivityStopped(Activity activity) { }
}
}
+570
View File
@@ -0,0 +1,570 @@
using Android;
using Android.App;
using Android.Content;
using Android.Nfc;
using Android.Nfc.Tech;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Plugin.NFC
{
/// <summary>
/// Android implementation of <see cref="INFC"/>
/// </summary>
public class NFCImplementation : INFC
{
public event EventHandler OnTagConnected;
public event EventHandler OnTagDisconnected;
public event NdefMessageReceivedEventHandler OnMessageReceived;
public event NdefMessagePublishedEventHandler OnMessagePublished;
public event TagDiscoveredEventHandler OnTagDiscovered;
public event EventHandler OniOSReadingSessionCancelled;
public event TagListeningStatusChangedEventHandler OnTagListeningStatusChanged;
readonly NfcAdapter _nfcAdapter;
bool _isListening;
bool _isWriting;
bool _isFormatting;
Tag _currentTag;
/// <summary>
/// Current Android <see cref="Context"/>
/// </summary>
Context CurrentContext => CrossNFC.AppContext;
/// <summary>
/// Current Android <see cref="Activity"/>
/// </summary>
Activity CurrentActivity => CrossNFC.GetCurrentActivity(true);
/// <summary>
/// Checks if NFC Feature is available
/// </summary>
public bool IsAvailable
{
get
{
if (CurrentContext.CheckCallingOrSelfPermission(Manifest.Permission.Nfc) != Android.Content.PM.Permission.Granted)
return false;
return _nfcAdapter != null;
}
}
/// <summary>
/// Checks if NFC Feature is enabled
/// </summary>
public bool IsEnabled => IsAvailable && _nfcAdapter.IsEnabled;
/// <summary>
/// Checks if writing mode is supported
/// </summary>
public bool IsWritingTagSupported => NFCUtils.IsWritingSupported();
/// <summary>
/// NFC configuration
/// </summary>
public NfcConfiguration Configuration { get; private set; }
/// <summary>
/// Default constructor
/// </summary>
public NFCImplementation()
{
_nfcAdapter = NfcAdapter.GetDefaultAdapter(CurrentContext);
Configuration = NfcConfiguration.GetDefaultConfiguration();
}
/// <summary>
/// Update NFC configuration
/// </summary>
/// <param name="configuration"><see cref="NfcConfiguration"/></param>
public void SetConfiguration(NfcConfiguration configuration) => Configuration.Update(configuration);
/// <summary>
/// Starts tags detection
/// </summary>
public void StartListening()
{
if (_nfcAdapter == null)
return;
var intent = new Intent(CurrentActivity, CurrentActivity.GetType()).AddFlags(ActivityFlags.SingleTop);
// We don't use MonoAndroid12.0 as targetframework for easier backward compatibility:
// MonoAndroid12.0 needs JDK 11.
PendingIntentFlags pendingIntentFlags = 0;
#if NET6_0_OR_GREATER
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.S)
pendingIntentFlags = PendingIntentFlags.Mutable;
#else
if ((int)Android.OS.Build.VERSION.SdkInt >= 31) //Android.OS.BuildVersionCodes.S
pendingIntentFlags = (PendingIntentFlags)33554432; //PendingIntentFlags.Mutable
#endif
var pendingIntent = PendingIntent.GetActivity(CurrentActivity, 0, intent, pendingIntentFlags);
var ndefFilter = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
ndefFilter.AddDataType("*/*");
var tagFilter = new IntentFilter(NfcAdapter.ActionTagDiscovered);
tagFilter.AddCategory(Intent.CategoryDefault);
var filters = new IntentFilter[] { ndefFilter, tagFilter };
_nfcAdapter.EnableForegroundDispatch(CurrentActivity, pendingIntent, filters, null);
_isListening = true;
OnTagListeningStatusChanged?.Invoke(_isListening);
}
/// <summary>
/// Stops tags detection
/// </summary>
public void StopListening()
{
DisablePublishing();
if (_nfcAdapter != null)
_nfcAdapter.DisableForegroundDispatch(CurrentActivity);
_isListening = false;
OnTagListeningStatusChanged?.Invoke(_isListening);
}
/// <summary>
/// Starts tag publishing (writing or formatting)
/// </summary>
/// <param name="clearMessage">Format tag</param>
public void StartPublishing(bool clearMessage = false)
{
if (!IsWritingTagSupported)
return;
_isWriting = true;
_isFormatting = clearMessage;
}
/// <summary>
/// Stops tag publishing
/// </summary>
public void StopPublishing() => DisablePublishing();
/// <summary>
/// Publish or write a message on a tag
/// </summary>
/// <param name="tagInfo">see <see cref="ITagInfo"/></param>
/// <param name="makeReadOnly">make tag read-only</param>
public void PublishMessage(ITagInfo tagInfo, bool makeReadOnly = false) => WriteOrClearMessage(tagInfo, false, makeReadOnly);
/// <summary>
/// Format tag
/// </summary>
/// <param name="tagInfo">see <see cref="ITagInfo"/></param>
public void ClearMessage(ITagInfo tagInfo) => WriteOrClearMessage(tagInfo, true);
/// <summary>
/// Write or Clear a NDEF message
/// </summary>
/// <param name="tagInfo"><see cref="ITagInfo"/></param>
/// <param name="clearMessage">Clear Message</param>
/// <param name="makeReadOnly">Make tag read-only</param>
internal void WriteOrClearMessage(ITagInfo tagInfo, bool clearMessage = false, bool makeReadOnly = false)
{
try
{
if (_currentTag == null)
throw new Exception(Configuration.Messages.NFCErrorMissingTag);
if (tagInfo == null)
throw new Exception(Configuration.Messages.NFCErrorMissingTagInfo);
var ndef = Ndef.Get(_currentTag);
if (ndef != null)
{
try
{
if (!ndef.IsWritable)
throw new Exception(Configuration.Messages.NFCErrorReadOnlyTag);
if (ndef.MaxSize < NFCUtils.GetSize(tagInfo.Records))
throw new Exception(Configuration.Messages.NFCErrorCapacityTag);
ndef.Connect();
OnTagConnected?.Invoke(null, EventArgs.Empty);
NdefMessage message = null;
if (clearMessage)
{
message = GetEmptyNdefMessage();
}
else
{
var records = new List<NdefRecord>();
for (var i = 0; i < tagInfo.Records.Length; i++)
{
var record = tagInfo.Records[i];
if (GetAndroidNdefRecord(record) is NdefRecord ndefRecord)
records.Add(ndefRecord);
}
if (records.Any())
message = new NdefMessage(records.ToArray());
}
if (message != null)
{
ndef.WriteNdefMessage(message);
if (!clearMessage && makeReadOnly)
{
if (!MakeReadOnly(ndef))
Console.WriteLine("Cannot lock tag");
}
var nTag = GetTagInfo(_currentTag, ndef.NdefMessage);
OnMessagePublished?.Invoke(nTag);
}
else
throw new Exception(Configuration.Messages.NFCErrorWrite);
}
catch (Android.Nfc.TagLostException tlex)
{
throw new Exception("Tag Lost Error: " + tlex.Message);
}
catch (Java.IO.IOException ioex)
{
throw new Exception("Tag IO Error: " + ioex.Message);
}
catch (Android.Nfc.FormatException fe)
{
throw new Exception("Tag Format Error: " + fe.Message);
}
catch (Exception ex)
{
throw new Exception("Tag Error:" + ex.Message);
}
finally
{
if (ndef.IsConnected)
ndef.Close();
_currentTag = null;
OnTagDisconnected?.Invoke(null, EventArgs.Empty);
}
}
else
throw new Exception(Configuration.Messages.NFCErrorNotCompliantTag);
}
catch (Exception ex)
{
StopPublishingAndThrowError(ex.Message);
}
}
/// <summary>
/// Handle Android OnNewIntent
/// </summary>
/// <param name="intent">Android <see cref="Intent"/></param>
internal void HandleNewIntent(Intent intent)
{
if (intent == null)
return;
if (intent.Action == NfcAdapter.ActionTagDiscovered || intent.Action == NfcAdapter.ActionNdefDiscovered)
{
_currentTag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
if (_currentTag != null)
{
var nTag = GetTagInfo(_currentTag);
if (_isWriting)
{
// Write mode
OnTagDiscovered?.Invoke(nTag, _isFormatting);
}
else
{
// Read mode
OnMessageReceived?.Invoke(nTag);
}
}
}
}
/// <summary>
/// Handle Android OnResume
/// </summary>
internal void HandleOnResume()
{
// Android 10 fix:
// If listening mode is already enable, we restart listening when activity is resumed
if (_isListening)
StartListening();
}
#region Private
/// <summary>
/// Stops publishing and throws error
/// </summary>
/// <param name="message">message</param>
void StopPublishingAndThrowError(string message)
{
StopPublishing();
throw new Exception(message);
}
/// <summary>
/// Deactivate publishing
/// </summary>
void DisablePublishing()
{
_isWriting = false;
_isFormatting = false;
}
/// <summary>
/// Transforms an array of <see cref="NdefRecord"/> into an array of <see cref="NFCNdefRecord"/>
/// </summary>
/// <param name="records">Array of <see cref="NdefRecord"/></param>
/// <returns>Array of <see cref="NFCNdefRecord"/></returns>
NFCNdefRecord[] GetRecords(NdefRecord[] records)
{
var results = new NFCNdefRecord[records.Length];
for (var i = 0; i < records.Length; i++)
{
var ndefRecord = new NFCNdefRecord
{
TypeFormat = (NFCNdefTypeFormat)records[i].Tnf,
Uri = records[i].ToUri()?.ToString(),
MimeType = records[i].ToMimeType(),
Payload = records[i].GetPayload()
};
results.SetValue(ndefRecord, i);
}
return results;
}
/// <summary>
/// Returns informations contains in NFC Tag
/// </summary>
/// <param name="tag">Android <see cref="Tag"/></param>
/// <param name="ndefMessage">Android <see cref="NdefMessage"/></param>
/// <returns><see cref="ITagInfo"/></returns>
ITagInfo GetTagInfo(Tag tag, NdefMessage ndefMessage = null)
{
if (tag == null)
return null;
var ndef = Ndef.Get(tag);
var nTag = new TagInfo(tag.GetId(), ndef != null);
if (ndef != null)
{
nTag.Capacity = ndef.MaxSize;
nTag.IsWritable = ndef.IsWritable;
if (ndefMessage == null)
ndefMessage = ndef.CachedNdefMessage;
if (ndefMessage != null)
{
var records = ndefMessage.GetRecords();
nTag.Records = GetRecords(records);
}
}
return nTag;
}
/// <summary>
/// Transforms a <see cref="NFCNdefRecord"/> into an Android <see cref="NdefRecord"/>
/// </summary>
/// <param name="record">Object <see cref="NFCNdefRecord"/></param>
/// <returns>Android <see cref="NdefRecord"/></returns>
NdefRecord GetAndroidNdefRecord(NFCNdefRecord record)
{
if (record == null)
return null;
NdefRecord ndefRecord = null;
switch (record.TypeFormat)
{
case NFCNdefTypeFormat.WellKnown:
var languageCode = record.LanguageCode;
if (string.IsNullOrWhiteSpace(languageCode)) languageCode = Configuration.DefaultLanguageCode;
ndefRecord = NdefRecord.CreateTextRecord(languageCode.Substring(0, 2), Encoding.UTF8.GetString(record.Payload));
break;
case NFCNdefTypeFormat.Mime:
ndefRecord = NdefRecord.CreateMime(record.MimeType, record.Payload);
break;
case NFCNdefTypeFormat.Uri:
ndefRecord = NdefRecord.CreateUri(Encoding.UTF8.GetString(record.Payload));
break;
case NFCNdefTypeFormat.External:
ndefRecord = NdefRecord.CreateExternal(record.ExternalDomain, record.ExternalType, record.Payload);
break;
case NFCNdefTypeFormat.Empty:
ndefRecord = GetEmptyNdefRecord();
break;
case NFCNdefTypeFormat.Unknown:
case NFCNdefTypeFormat.Unchanged:
case NFCNdefTypeFormat.Reserved:
default:
break;
}
return ndefRecord;
}
/// <summary>
/// Returns an empty Android <see cref="NdefRecord"/>
/// </summary>
/// <returns>Android <see cref="NdefRecord"/></returns>
NdefRecord GetEmptyNdefRecord()
{
var empty = Array.Empty<byte>();
return new NdefRecord(NdefRecord.TnfEmpty, empty, empty, empty);
}
/// <summary>
/// Returns an empty Android <see cref="NdefMessage"/>
/// </summary>
/// <returns>Android <see cref="NdefMessage"/></returns>
NdefMessage GetEmptyNdefMessage()
{
var records = new NdefRecord[1];
records[0] = GetEmptyNdefRecord();
return new NdefMessage(records);
}
/// <summary>
/// Make a tag read-only
/// WARNING: This operation is permanent
/// </summary>
/// <param name="ndef"><see cref="Ndef"/></param>
/// <returns>boolean</returns>
bool MakeReadOnly(Ndef ndef)
{
if (ndef == null)
return false;
var result = false;
var newConnection = false;
if (!ndef.IsConnected)
{
newConnection = true;
ndef.Connect();
}
if (ndef.CanMakeReadOnly())
result = ndef.MakeReadOnly();
if (newConnection && ndef.IsConnected)
ndef.Close();
return result;
}
#endregion
#region NFC Status Event Listener
NfcBroadcastReceiver _nfcBroadcastReceiver;
event OnNfcStatusChangedEventHandler _onNfcStatusChangedInternal;
public event OnNfcStatusChangedEventHandler OnNfcStatusChanged
{
add
{
var wasRunning = _onNfcStatusChangedInternal != null;
_onNfcStatusChangedInternal += value;
if (!wasRunning && _onNfcStatusChangedInternal != null)
{
RegisterListener();
}
}
remove
{
var wasRunning = _onNfcStatusChangedInternal != null;
_onNfcStatusChangedInternal -= value;
if (wasRunning && _onNfcStatusChangedInternal == null)
{
UnRegisterListener();
}
}
}
/// <summary>
/// Register NFC Broadcast Receiver
/// </summary>
void RegisterListener()
{
_nfcBroadcastReceiver = new NfcBroadcastReceiver(OnNfcStatusChange);
CurrentContext?.RegisterReceiver(_nfcBroadcastReceiver, new IntentFilter(NfcAdapter.ActionAdapterStateChanged));
}
/// <summary>
/// Unregister NFC Broadcast Receiver
/// </summary>
void UnRegisterListener()
{
if (_nfcBroadcastReceiver == null)
return;
try
{
CurrentContext?.UnregisterReceiver(_nfcBroadcastReceiver);
}
catch (Java.Lang.IllegalArgumentException ex)
{
throw new Exception("NFC Broadcast Receiver Error: " + ex.Message);
}
_nfcBroadcastReceiver.Dispose();
_nfcBroadcastReceiver = null;
}
/// <summary>
/// Called when NFC status has changed
/// </summary>
void OnNfcStatusChange() => _onNfcStatusChangedInternal?.Invoke(IsEnabled);
/// <summary>
/// Broadcast Receiver to check NFC feature availability
/// </summary>
[BroadcastReceiver(Enabled = true, Exported = false, Label = "NFC Status Broadcast Receiver")]
class NfcBroadcastReceiver : BroadcastReceiver
{
Action _onChanged;
public NfcBroadcastReceiver() { }
public NfcBroadcastReceiver(Action onChanged)
{
_onChanged = onChanged;
}
public override async void OnReceive(Context context, Intent intent)
{
if (intent.Action == NfcAdapter.ActionAdapterStateChanged)
{
var state = intent.GetIntExtra(NfcAdapter.ExtraAdapterState, default);
if (state == NfcAdapter.StateOff || state == NfcAdapter.StateOn)
{
// await 1500ms to ensure that the status updates
await Task.Delay(1500);
_onChanged?.Invoke();
}
}
}
}
#endregion
}
}
+107
View File
@@ -0,0 +1,107 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.44">
<PropertyGroup>
<!--Work around so the conditions work below-->
<TargetFrameworks>netstandard1.0;netstandard2.0;Xamarin.iOS10;MonoAndroid10.0;net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
<AssemblyName>Plugin.NFC</AssemblyName>
<RootNamespace>Plugin.NFC</RootNamespace>
<PackageId>Plugin.NFC</PackageId>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<AssemblyFileVersion>1.0.0.0</AssemblyFileVersion>
<Version>1.0.0.0</Version>
<PackageVersion>1.0.0.0</PackageVersion>
<PackOnBuild>true</PackOnBuild>
<NeutralLanguage>en</NeutralLanguage>
<DefineConstants>$(DefineConstants);</DefineConstants>
<NoWarn>CS0067</NoWarn>
<UseFullSemVerForNuGet>false</UseFullSemVerForNuGet>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<LangVersion>latest</LangVersion>
<DebugType>portable</DebugType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/franckbour/Plugin.NFC</PackageProjectUrl>
<RepositoryUrl>https://github.com/franckbour/Plugin.NFC</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/franckbour/Plugin.NFC/blob/master/CHANGELOG.md</PackageReleaseNotes>
<PackageIconUrl>https://github.com/franckbour/Plugin.NFC/raw/master/art/nfc128.png</PackageIconUrl>
<PackageIcon>icon.png</PackageIcon>
<PackageTags>maui, xamarin, ios, android, xamarin.forms, plugin, NFC</PackageTags>
<Title>NFC Plugin for Xamarin</Title>
<Summary>A Cross-Platform plugin to easily read and write NFC tags.</Summary>
<Description>Cross-Platform NFC (Near Field Communication) plugin to easily read and write NFC tags in your application.</Description>
<Owners>Franck Bour</Owners>
<Authors>Franck Bour</Authors>
<Copyright>Copyright 2022</Copyright>
<!-- When built in 2019 will remove extra references on pack for iOS in System.Drawing.Common -->
<DisableExtraReferences>false</DisableExtraReferences>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- sourcelink: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- sourcelink: Embed source files that are not tracked by the source control manager in the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- sourcelink: Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup>
<_NET8 Condition=" $(TargetFramework.Contains('net8')) ">true</_NET8>
<_DROID Condition=" $(TargetFramework.ToLowerInvariant().Contains('droid')) ">true</_DROID>
<_IOS Condition=" $(TargetFramework.ToLowerInvariant().Contains('ios')) ">true</_IOS>
<_MOBILE Condition=" '$(_DROID)' == 'true' OR '$(_IOS)' == 'true' ">true</_MOBILE>
<DefineConstants Condition=" '$(_DROID)' == 'true' ">$(DefineConstants);__ANDROID__;__MOBILE__;</DefineConstants>
<DefineConstants Condition=" '$(_IOS)' == 'true' ">$(DefineConstants);__IOS__;__MOBILE__;</DefineConstants>
<DefineConstants Condition=" '$(_NET8)' == 'true' ">$(DefineConstants);__NET8__;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(_NET8)' == 'true' AND '$(_MOBILE)' == 'true' ">
<SupportedOSPlatformVersion Condition="'$(_IOS)' == 'true'">11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="'$(_DROID)' == 'true'">21.0</SupportedOSPlatformVersion>
</PropertyGroup>
<!-- Deterministic Builds (Azure Pipelines) -->
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\art\nfc128.png" PackagePath="icon.png" Pack="true" />
<Compile Include="**\*.shared.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net8.0' ">
</ItemGroup>
<ItemGroup Condition=" '$(_DROID)' == 'true' ">
<Compile Include="**\*.android.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(_IOS)' == 'true' ">
<Compile Include="**\*.iOS.cs" />
</ItemGroup>
</Project>
+73
View File
@@ -0,0 +1,73 @@
using System;
namespace Plugin.NFC
{
/// <summary>
/// Cross NFC
/// </summary>
public static partial class CrossNFC
{
static Lazy<INFC> _implementation = new Lazy<INFC>(() => CreateNFC(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
/// <summary>
/// Gets if the plugin is supported on the current platform.
/// </summary>
public static bool IsSupported => _implementation.Value != null;
/// <summary>
/// Legacy Mode (Supporting Mifare Classic on iOS)
/// </summary>
static bool _legacy = false;
public static bool Legacy
{
get
{
return _legacy;
}
set
{
_legacy = value;
_implementation = new Lazy<INFC>(() => CreateNFC(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
}
}
/// <summary>
/// Current plugin implementation to use
/// </summary>
public static INFC Current
{
get
{
INFC ret = _implementation.Value;
if (ret == null)
{
throw NotImplementedInReferenceAssembly();
}
return ret;
}
}
static INFC CreateNFC()
{
#if NETSTANDARD || !__MOBILE__
return null;
#elif __IOS__
ObjCRuntime.Class.ThrowOnInitFailure = false;
if (NFCUtils.IsWritingSupported() && !Legacy)
return new NFCImplementation();
return new NFCImplementation_Before_iOS13();
#else
#pragma warning disable IDE0022 // Use expression body for methods
return new NFCImplementation();
#pragma warning restore IDE0022 // Use expression body for methods
#endif
}
internal static Exception NotImplementedInReferenceAssembly() =>
new NotImplementedException("This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
}
}
+118
View File
@@ -0,0 +1,118 @@
using System;
namespace Plugin.NFC
{
#region Event delegates
public delegate void NdefMessageReceivedEventHandler(ITagInfo tagInfo);
public delegate void NdefMessagePublishedEventHandler(ITagInfo tagInfo);
public delegate void TagDiscoveredEventHandler(ITagInfo tagInfo, bool format);
public delegate void OnNfcStatusChangedEventHandler(bool isEnabled);
public delegate void TagListeningStatusChangedEventHandler(bool isListening);
#endregion
/// <summary>
/// Main interface for NFC
/// </summary>
public interface INFC
{
/// <summary>
/// Checks if NFC Feature is available
/// </summary>
bool IsAvailable { get; }
/// <summary>
/// Checks if NFC Feature is enabled
/// </summary>
bool IsEnabled { get; }
/// <summary>
/// Checks if writing mode is supported
/// </summary>
bool IsWritingTagSupported { get; }
/// <summary>
/// NFC Configuration
/// </summary>
NfcConfiguration Configuration { get; }
/// <summary>
/// Set Nfc configuration
/// </summary>
/// <param name="configuration"><see cref="NfcConfiguration"/></param>
void SetConfiguration(NfcConfiguration configuration);
/// <summary>
/// Starts tags detection
/// </summary>
void StartListening();
/// <summary>
/// Stops tags detection
/// </summary>
void StopListening();
/// <summary>
/// Starts tag publishing (writing or formatting)
/// </summary>
/// <param name="clearMessage">Format tag</param>
void StartPublishing(bool clearMessage = false);
/// <summary>
/// Stops tag publishing
/// </summary>
void StopPublishing();
/// <summary>
/// Publish or write a message on a tag
/// </summary>
/// <param name="tagInfo">see <see cref="ITagInfo"/></param>
/// <param name="makeReadOnly">make tag read-only</param>
void PublishMessage(ITagInfo tagInfo, bool makeReadOnly = false);
/// <summary>
/// Format tag
/// </summary>
/// <param name="tagInfo">see <see cref="ITagInfo"/></param>
void ClearMessage(ITagInfo tagInfo);
/// <summary>
/// Event raised when tag is connected
/// </summary>
event EventHandler OnTagConnected;
/// <summary>
/// Event raised when tag is disconnected
/// </summary>
event EventHandler OnTagDisconnected;
/// <summary>
/// Event raised when ndef message is received
/// </summary>
event NdefMessageReceivedEventHandler OnMessageReceived;
/// <summary>
/// Event raised when a tag is discovered (Editing)
/// </summary>
event TagDiscoveredEventHandler OnTagDiscovered;
/// <summary>
/// Event raised when ndef message has been published
/// </summary>
event NdefMessagePublishedEventHandler OnMessagePublished;
/// <summary>
/// Event raised when iOS NFC reading session is cancelled
/// </summary>
event EventHandler OniOSReadingSessionCancelled;
/// <summary>
/// Event raised when NFC status changes
/// </summary>
event OnNfcStatusChangedEventHandler OnNfcStatusChanged;
/// <summary>
/// Event raised when NFC listener status changes
/// </summary>
event TagListeningStatusChangedEventHandler OnTagListeningStatusChanged;
}
}
+104
View File
@@ -0,0 +1,104 @@
namespace Plugin.NFC
{
/// <summary>
/// Interface for ITagInfo
/// </summary>
public interface ITagInfo
{
/// <summary>
/// Tag Raw Identifier
/// </summary>
byte[] Identifier { get; }
/// <summary>
/// Tag Serial Number
/// </summary>
string SerialNumber { get; }
/// <summary>
/// Writable tag
/// </summary>
bool IsWritable { get; set; }
/// <summary>
/// Empty tag
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Supported tag
/// </summary>
bool IsSupported { get; }
/// <summary>
/// Capacity of tag in bytes
/// </summary>
int Capacity { get; set; }
/// <summary>
/// Array of <see cref="NFCNdefRecord"/> of tag
/// </summary>
NFCNdefRecord[] Records { get; set; }
}
/// <summary>
/// Class describing the information containing within a NFC tag
/// </summary>
public class NFCNdefRecord
{
/// <summary>
/// NDEF Type
/// </summary>
public NFCNdefTypeFormat TypeFormat { get; set; }
/// <summary>
/// MimeType used for <see cref="NFCNdefTypeFormat.Mime"/> type
/// </summary>
public string MimeType { get; set; } = "text/plain";
/// <summary>
/// External domain used for <see cref="NFCNdefTypeFormat.External"/> type
/// </summary>
public string ExternalDomain { get; set; }
/// <summary>
/// External type used for <see cref="NFCNdefTypeFormat.External"/> type
/// </summary>
public string ExternalType { get; set; }
/// <summary>
/// Payload
/// </summary>
public byte[] Payload { get; set; }
/// <summary>
/// Uri
/// </summary>
public string Uri { get; set; }
/// <summary>
/// String formatted payload
/// </summary>
public string Message => NFCUtils.GetMessage(TypeFormat, Payload, Uri);
/// <summary>
/// Two letters ISO 639-1 Language Code (ex: en, fr, de...)
/// </summary>
public string LanguageCode { get; set; }
}
/// <summary>
/// Enumeration of NDEF type
/// </summary>
public enum NFCNdefTypeFormat
{
Empty = 0x00,
WellKnown = 0x01,
Mime = 0x02,
Uri = 0x03,
External = 0x04,
Unknown = 0x05,
Unchanged = 0x06,
Reserved = 0x07
}
}
+113
View File
@@ -0,0 +1,113 @@
using System.Linq;
using System.Text;
#if __IOS__
using UIKit;
#endif
namespace Plugin.NFC
{
/// <summary>
/// NFC tools
/// </summary>
public static class NFCUtils
{
/// <summary>
/// Returns the content size of an array of <see cref="NFCNdefRecord"/>
/// </summary>
/// <param name="records">array of <see cref="NFCNdefRecord"/></param>
/// <returns>Content size</returns>
internal static int GetSize(NFCNdefRecord[] records)
{
var size = 0;
if (records != null && records.Length > 0)
{
for (var i = 0; i < records.Length; i++)
{
if (records[i] != null)
size += records[i].Payload.Length;
}
}
return size;
}
/// <summary>
/// Returns the string formatted payload
/// </summary>
/// <param name="type">type of <see cref="NFCNdefTypeFormat"/></param>
/// <param name="payload">record payload</param>
/// <param name="uri">record uri</param>
/// <returns>String formatted payload</returns>
internal static string GetMessage(NFCNdefTypeFormat type, byte[] payload, string uri)
{
string message;
if (!string.IsNullOrWhiteSpace(uri))
message = uri;
else
{
if (type == NFCNdefTypeFormat.WellKnown)
{
// NDEF_WELLKNOWN Text record
var status = payload[0];
var enc = status & 0x80;
var languageCodeLength = status & 0x3F;
if (enc == 0)
message = Encoding.UTF8.GetString(payload, languageCodeLength + 1, payload.Length - languageCodeLength - 1);
else
message = Encoding.Unicode.GetString(payload, languageCodeLength + 1, payload.Length - languageCodeLength - 1);
}
else
{
// Other NDEF types
message = Encoding.UTF8.GetString(payload, 0, payload.Length);
}
}
return message;
}
/// <summary>
/// Transforms a string message into an array of bytes
/// </summary>
/// <param name="text">text message</param>
/// <returns>Array of bytes</returns>
public static byte[] EncodeToByteArray(string text) => Encoding.UTF8.GetBytes(text);
/// <summary>
/// Returns the string formatted payload
/// </summary>
/// <param name="record">Object <see cref="NFCNdefRecord"/></param>
/// <returns>String formatted payload</returns>
public static string GetMessage(NFCNdefRecord record)
{
if (record == null)
return string.Empty;
return GetMessage(record.TypeFormat, record.Payload, record.Uri);
}
/// <summary>
/// Convert bytes array into hexadecimal string
/// </summary>
/// <param name="bytes">Bytes Array</param>
/// <param name="separator">Separator</param>
/// <returns>Hexadecimal string</returns>
public static string ByteArrayToHexString(byte[] bytes, string separator = null)
{
return bytes == null ? string.Empty : string.Join(separator ?? string.Empty, bytes.Select(b => b.ToString("X2")));
}
/// <summary>
/// Checks if writing tags is supported
/// </summary>
/// <returns>boolean</returns>
public static bool IsWritingSupported()
{
#if __IOS__
var splitted = UIDevice.CurrentDevice.SystemVersion?.Split('.');
if (splitted != null && splitted.Length > 0 && int.TryParse(splitted[0], out var majorVersion))
return majorVersion >= 13;
return false;
#else
return true;
#endif
}
}
}
@@ -0,0 +1,282 @@
namespace Plugin.NFC
{
/// <summary>
/// NFC Configuration class
/// </summary>
public class NfcConfiguration
{
/// <summary>
/// List of user defined messages
/// </summary>
public UserDefinedMessages Messages { get; set; }
/// <summary>
/// Sets ISO 639-1 Language Code for all ndef records (default is "en")
/// </summary>
public string DefaultLanguageCode { get; set; }
/// <summary>
/// Update Nfc Configuration with a new configuration object
/// </summary>
/// <param name="newCfg"><see cref="NfcConfiguration"/></param>
public void Update(NfcConfiguration newCfg)
{
if (newCfg == null || newCfg.Messages == null)
return;
Messages = newCfg.Messages;
DefaultLanguageCode = newCfg.DefaultLanguageCode;
}
/// <summary>
/// Get the default Nfc configuration
/// </summary>
/// <returns>Default <see cref="NfcConfiguration"/></returns>
public static NfcConfiguration GetDefaultConfiguration()
=> new NfcConfiguration { Messages = new UserDefinedMessages(), DefaultLanguageCode = "en" };
}
/// <summary>
/// User defined UI messages
/// </summary>
public class UserDefinedMessages
{
string _nfcSessionInvalidated = "Session Invalidated";
string _nfcSessionInvalidatedButton = "OK";
string _nfcWritingNotSupported = "Writing NFC Tag is not supported on this device";
string _nfcDialogAlertMessage = "Please hold your phone near a NFC tag";
string _nfcErrorRead = "Read error. Please try again";
string _nfcErrorEmptyTag = "Tag is empty";
string _nfcErrorReadOnlyTag = "Tag is not writable";
string _nfcErrorCapacityTag = "Tag's capacity is too low";
string _nfcErrorMissingTag = "Tag is missing";
string _nfcErrorMissingTagInfo = "No Tag Informations: nothing to write";
string _nfcErrorNotSupportedTag = "Tag is not supported";
string _nfcErrorNotCompliantTag = "Tag is not NDEF compliant";
string _nfcErrorWrite = "Nothing to write";
string _nfcSuccessRead = "Read Operation Successful";
string _nfcSuccessWrite = "Write Operation Successful";
string _nfcSuccessClear = "Clear Operation Successful";
string _nfcSessionTimeout = "session timeout";
/// <summary>
/// Session timeout
/// </summary>
public string NFCSessionTimeout
{
get => _nfcSessionTimeout;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSessionTimeout = value;
}
}
/// <summary>
/// Session invalidated
/// </summary>
public string NFCSessionInvalidatedButton
{
get => _nfcSessionInvalidatedButton;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSessionInvalidatedButton = value;
}
}
/// <summary>
/// Session invalidated
/// </summary>
public string NFCSessionInvalidated
{
get => _nfcSessionInvalidated;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSessionInvalidated = value;
}
}
/// <summary>
/// Writing feature not supported
/// </summary>
public string NFCWritingNotSupported
{
get => _nfcWritingNotSupported;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcWritingNotSupported = value;
}
}
/// <summary>
/// [iOS] NFC Scan dialog alert message
/// </summary>
public string NFCDialogAlertMessage
{
get => _nfcDialogAlertMessage;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcDialogAlertMessage = value;
}
}
/// <summary>
/// Read operation error
/// </summary>
public string NFCErrorRead
{
get => _nfcErrorRead;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorRead = value;
}
}
/// <summary>
/// Write operation error
/// </summary>
public string NFCErrorWrite
{
get => _nfcErrorWrite;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorWrite = value;
}
}
/// <summary>
/// Empty tag error
/// </summary>
public string NFCErrorEmptyTag
{
get => _nfcErrorEmptyTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorEmptyTag = value;
}
}
/// <summary>
/// Read-only tag error
/// </summary>
public string NFCErrorReadOnlyTag
{
get => _nfcErrorReadOnlyTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorReadOnlyTag = value;
}
}
/// <summary>
/// Tag capacity error
/// </summary>
public string NFCErrorCapacityTag
{
get => _nfcErrorCapacityTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorCapacityTag = value;
}
}
/// <summary>
/// Missing tag error
/// </summary>
public string NFCErrorMissingTag
{
get => _nfcErrorMissingTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorMissingTag = value;
}
}
/// <summary>
/// Missing tag info error
/// </summary>
public string NFCErrorMissingTagInfo
{
get => _nfcErrorMissingTagInfo;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorMissingTagInfo = value;
}
}
/// <summary>
/// Not supported tag error
/// </summary>
public string NFCErrorNotSupportedTag
{
get => _nfcErrorNotSupportedTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorNotSupportedTag = value;
}
}
/// <summary>
/// Not NDEF compliant tag error
/// </summary>
public string NFCErrorNotCompliantTag
{
get => _nfcErrorNotCompliantTag;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcErrorNotCompliantTag = value;
}
}
/// <summary>
/// [iOS] Successful read operation message
/// </summary>
public string NFCSuccessRead
{
get => _nfcSuccessRead;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSuccessRead = value;
}
}
/// <summary>
/// [iOS] Successful write operation message
/// </summary>
public string NFCSuccessWrite
{
get => _nfcSuccessWrite;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSuccessWrite = value;
}
}
/// <summary>
/// [iOS] Successful clear operation message
/// </summary>
public string NFCSuccessClear
{
get => _nfcSuccessClear;
set
{
if (!string.IsNullOrWhiteSpace(value))
_nfcSuccessClear = value;
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
namespace Plugin.NFC
{
/// <summary>
/// Default implementation of <see cref="ITagInfo"/>
/// </summary>
public class TagInfo : ITagInfo
{
public byte[] Identifier { get; }
/// <summary>
/// Tag Serial Number
/// </summary>
public string SerialNumber { get; }
/// <summary>
/// Writable tag
/// </summary>
public bool IsWritable { get; set; }
/// <summary>
/// Capacity of tag in bytes
/// </summary>
public int Capacity { get; set; }
/// <summary>
/// Array of <see cref="NFCNdefRecord"/> of tag
/// </summary>
public NFCNdefRecord[] Records { get; set; }
/// <summary>
/// Empty tag
/// </summary>
public bool IsEmpty => Records == null || Records.Length == 0 || Records[0] == null ||Records[0].TypeFormat == NFCNdefTypeFormat.Empty;
/// <summary>
///
/// </summary>
public bool IsSupported { get; private set; }
/// <summary>
/// Default constructor
/// </summary>
public TagInfo()
{
IsSupported = true;
}
/// <summary>
/// Custom contructor
/// </summary>
/// <param name="identifier">Tag Identifier</param>
/// <param name="isNdef">Is Ndef tag</param>
public TagInfo(byte[] identifier, bool isNdef = true)
{
Identifier = identifier;
SerialNumber = NFCUtils.ByteArrayToHexString(identifier);
IsSupported = isNdef;
}
public override string ToString() => $"TagInfo: identifier: {Identifier}, SerialNumber:{SerialNumber}, Capacity:{Capacity} bytes, IsSupported:{IsSupported}, IsEmpty:{IsEmpty}, IsWritable:{IsWritable}";
}
}
File diff suppressed because it is too large Load Diff