diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml
index f02741d2da..22c47f6bef 100644
--- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml
+++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml
@@ -19,6 +19,9 @@
+
+
+
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index d8202064e7..b991d8067f 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -20,7 +20,7 @@ namespace Avalonia
public static AppBuilder UseAndroid(this AppBuilder builder)
{
return builder
- .UseStandardRuntimePlatformSubsystem()
+ .UseAndroidRuntimePlatformSubsystem()
.UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
.UseSkia();
}
diff --git a/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs b/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs
new file mode 100644
index 0000000000..f38492005a
--- /dev/null
+++ b/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs
@@ -0,0 +1,52 @@
+using System;
+using Android.Content.PM;
+using Android.Content;
+using Avalonia.Platform;
+using App = Android.App.Application;
+using System.Reflection;
+
+namespace Avalonia
+{
+ internal static class AndroidRuntimePlatformServices
+ {
+ public static AppBuilder UseAndroidRuntimePlatformSubsystem(this AppBuilder builder)
+ {
+ builder.UseRuntimePlatformSubsystem(() => Register(builder.ApplicationType?.Assembly), nameof(AndroidRuntimePlatform));
+ return builder;
+ }
+
+ public static void Register(Assembly? assembly = null)
+ {
+ AssetLoader.RegisterResUriParsers();
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToSingleton()
+ .Bind().ToConstant(new StandardAssetLoader(assembly));
+ }
+ }
+
+
+ internal class AndroidRuntimePlatform : StandardRuntimePlatform
+ {
+ private static readonly Lazy Info = new(() =>
+ {
+ var isDesktop = IsRunningOnDesktop(App.Context);
+ var isTv = IsRunningOnTv(App.Context);
+
+ return new RuntimePlatformInfo
+ {
+ IsDesktop = isDesktop,
+ IsMobile = !isTv && !isDesktop,
+ IsTV = isTv
+ };
+ });
+
+ private static bool IsRunningOnDesktop(Context context) =>
+ context.PackageManager.HasSystemFeature("org.chromium.arc") ||
+ context.PackageManager.HasSystemFeature("org.chromium.arc.device_management");
+
+ private static bool IsRunningOnTv(Context context) =>
+ context.PackageManager.HasSystemFeature(PackageManager.FeatureLeanback);
+
+ public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value;
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
index c6efadc99a..565f44ec40 100644
--- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs
+++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
@@ -13,15 +13,17 @@ namespace Avalonia.Platform
public record struct RuntimePlatformInfo
{
public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop :
- IsMobile ? FormFactorType.Mobile : FormFactorType.Unknown;
+ IsMobile ? FormFactorType.Mobile : IsTV ? FormFactorType.TV : FormFactorType.Unknown;
public bool IsDesktop { get; set; }
public bool IsMobile { get; set; }
+ public bool IsTV { get; set; }
}
public enum FormFactorType
{
Unknown,
Desktop,
- Mobile
+ Mobile,
+ TV
}
}
diff --git a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs
index eee2b70c9c..8709fd79fd 100644
--- a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs
+++ b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs
@@ -30,10 +30,12 @@ internal class BrowserRuntimePlatform : StandardRuntimePlatform
private static readonly Lazy Info = new(() =>
{
var isMobile = AvaloniaModule.IsMobile();
+ var isTv = AvaloniaModule.IsTv();
var result = new RuntimePlatformInfo
{
- IsMobile = isMobile,
- IsDesktop = !isMobile
+ IsMobile = isMobile && !isTv,
+ IsDesktop = !isMobile && !isTv,
+ IsTV = isTv
};
return result;
diff --git a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs
index cb7aabbc39..b48a00919c 100644
--- a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs
+++ b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs
@@ -33,7 +33,10 @@ internal static partial class AvaloniaModule
[JSImport("Caniuse.isMobile", AvaloniaModule.MainModuleName)]
public static partial bool IsMobile();
-
+
+ [JSImport("Caniuse.isTv", AvaloniaModule.MainModuleName)]
+ public static partial bool IsTv();
+
[JSImport("registerServiceWorker", AvaloniaModule.MainModuleName)]
public static partial void RegisterServiceWorker(string path, string? scope);
}
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts
index 8fdc3a5c01..54e6900abc 100644
--- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts
+++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts
@@ -14,4 +14,8 @@ export class Caniuse {
const regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw(n|u)|c55\/|capi|ccwa|cdm|cell|chtm|cldc|cmd|co(mp|nd)|craw|da(it|ll|ng)|dbte|dcs|devi|dica|dmob|do(c|p)o|ds(12|d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(|_)|g1 u|g560|gene|gf5|gmo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i;
return regex1.test(userAgent) || regex2.test(userAgent.substr(0, 4));
}
+
+ public static isTv(): boolean {
+ return navigator.userAgent.includes("SmartTV");
+ }
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
index a07595a35b..1ac733a345 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
@@ -4,6 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.MarkupExtensions;
+///
public sealed class OnFormFactorExtension : OnFormFactorExtensionBase