@@ -1751,6 +1751,279 @@ public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet att
17511751 Assert . IsTrue ( didStart , "Activity should have started." ) ;
17521752 }
17531753
1754+ [ TestCase ( "llvm-ir" , AndroidRuntime . CoreCLR ) ]
1755+ [ TestCase ( "trimmable" , AndroidRuntime . CoreCLR ) ]
1756+ [ TestCase ( "trimmable" , AndroidRuntime . NativeAOT ) ]
1757+ public void AppCompatJavaAliasCastsAndInflation (
1758+ string typemapImplementation ,
1759+ AndroidRuntime runtime )
1760+ {
1761+ const string expectedLogcatOutput = "APPCOMPAT_ALIAS_CASTS_PASS" ;
1762+
1763+ if ( IgnoreUnsupportedConfiguration ( runtime , release : true ) ) {
1764+ return ;
1765+ }
1766+
1767+ var packageSuffix = $ "appcompataliascasts{ typemapImplementation . Replace ( "-" , "" ) } ";
1768+ var packageName = PackageUtils . MakePackageName ( runtime , packageSuffix ) ;
1769+ var proj = new XamarinAndroidApplicationProject (
1770+ packageName : packageName ) {
1771+ IsRelease = true ,
1772+ } ;
1773+ proj . SetRuntime ( runtime ) ;
1774+ proj . SetRuntimeIdentifiers ( new [ ] { DeviceAbi } ) ;
1775+ proj . SetProperty ( "AndroidTypeMapImplementation" , typemapImplementation ) ;
1776+ proj . SetDefaultTargetDevice ( ) ;
1777+ proj . PackageReferences . Add ( new Package {
1778+ Id = "Xamarin.AndroidX.AppCompat" ,
1779+ Version = "1.7.1.3" ,
1780+ } ) ;
1781+ proj . AndroidResources . Add ( new AndroidItem . AndroidResource ( "Resources\\ values\\ styles.xml" ) {
1782+ TextContent = ( ) => """
1783+ <?xml version="1.0" encoding="utf-8"?>
1784+ <resources>
1785+ <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar" />
1786+ </resources>
1787+ """ ,
1788+ } ) ;
1789+ proj . AndroidResources . Add ( new AndroidItem . AndroidResource ( "Resources\\ layout\\ alias_casts.xml" ) {
1790+ TextContent = ( ) => """
1791+ <?xml version="1.0" encoding="utf-8"?>
1792+ <LinearLayout
1793+ xmlns:android="http://schemas.android.com/apk/res/android"
1794+ android:layout_width="match_parent"
1795+ android:layout_height="match_parent"
1796+ android:orientation="vertical">
1797+ <androidx.appcompat.widget.Toolbar
1798+ android:id="@+id/toolbar"
1799+ android:layout_width="match_parent"
1800+ android:layout_height="wrap_content" />
1801+ <androidx.appcompat.widget.AppCompatImageButton
1802+ android:id="@+id/image_button"
1803+ android:layout_width="wrap_content"
1804+ android:layout_height="wrap_content"
1805+ android:src="@android:drawable/ic_menu_add" />
1806+ </LinearLayout>
1807+ """ ,
1808+ } ) ;
1809+ proj . MainActivity = """
1810+ using System;
1811+
1812+ using Android.App;
1813+ using Android.Content;
1814+ using Android.OS;
1815+ using Android.Runtime;
1816+ using Android.Util;
1817+ using Android.Views;
1818+
1819+ using AndroidX.AppCompat.App;
1820+ using AndroidX.AppCompat.Widget;
1821+ using AndroidX.Core.View;
1822+
1823+ using Java.Interop;
1824+
1825+ namespace UnnamedProject
1826+ {
1827+ [Activity (
1828+ Label = "AppCompat alias casts",
1829+ MainLauncher = true,
1830+ Theme = "@style/AppTheme")]
1831+ public class MainActivity : AppCompatActivity
1832+ {
1833+ public MainActivity ()
1834+ {
1835+ }
1836+
1837+ protected MainActivity (IntPtr handle, JniHandleOwnership transfer)
1838+ : base (handle, transfer)
1839+ {
1840+ }
1841+
1842+ protected override void OnCreate (Bundle savedInstanceState)
1843+ {
1844+ base.OnCreate (savedInstanceState);
1845+ SetContentView (Resource.Layout.alias_casts);
1846+
1847+ VerifyInflatedViews ();
1848+ VerifyManagedCreatedViews ();
1849+ VerifyCallerDirectedGenericWrapper ();
1850+
1851+ Log.Info ("JavaAliasCasts", "APPCOMPAT_ALIAS_CASTS_PASS");
1852+ }
1853+
1854+ void VerifyInflatedViews ()
1855+ {
1856+ const bool allowRegisteredAliasInflation = __ALLOW_REGISTERED_ALIAS_INFLATION__;
1857+
1858+ var toolbarView = FindViewById (Resource.Id.toolbar);
1859+ Require (toolbarView != null, "Inflated Toolbar was not found.");
1860+ Require (
1861+ toolbarView.GetType () == typeof (AndroidX.AppCompat.Widget.Toolbar),
1862+ $"Expected most-derived Toolbar binding; {Describe (toolbarView)}.");
1863+
1864+ var toolbar = JavaObjectExtensions.JavaCast<AndroidX.AppCompat.Widget.Toolbar> (toolbarView);
1865+ var toolbarAs = JavaPeerableExtensions.JavaAs<AndroidX.AppCompat.Widget.Toolbar> (toolbarView);
1866+ Require (ReferenceEquals (toolbarView, toolbar), "JavaCast<Toolbar> did not preserve the existing peer.");
1867+ Require (ReferenceEquals (toolbarView, toolbarAs), "JavaAs<Toolbar> did not preserve the existing peer.");
1868+ Require (
1869+ ReferenceEquals (toolbarView, FindViewById (Resource.Id.toolbar)),
1870+ "Repeated Toolbar lookup did not preserve peer identity.");
1871+
1872+ var imageView = FindViewById (Resource.Id.image_button);
1873+ Require (imageView != null, "Inflated AppCompatImageButton was not found.");
1874+ Require (
1875+ imageView.GetType () == typeof (AppCompatImageButton) ||
1876+ (allowRegisteredAliasInflation && imageView.GetType () == typeof (AppCompatImageButtonAlias)),
1877+ $"Expected the canonical AppCompatImageButton binding; {Describe (imageView)}.");
1878+ Require (
1879+ ReferenceEquals (imageView, FindViewById (Resource.Id.image_button)),
1880+ "Repeated AppCompatImageButton lookup did not preserve peer identity.");
1881+
1882+ using var untypedImage = new Java.Lang.Object (
1883+ imageView.Handle,
1884+ JniHandleOwnership.DoNotTransfer | JniHandleOwnership.DoNotRegister);
1885+ var alias = JavaObjectExtensions.JavaCast<AppCompatImageButtonAlias> (untypedImage);
1886+ try {
1887+ using var aliasAs = JavaPeerableExtensions.JavaAs<AppCompatImageButtonAlias> (untypedImage);
1888+ Require (alias != null, "JavaCast did not select the concrete AppCompatImageButton alias.");
1889+ Require (aliasAs != null, "JavaAs did not select the concrete AppCompatImageButton alias.");
1890+ Require (
1891+ JNIEnv.IsSameObject (imageView.Handle, alias.Handle),
1892+ "Concrete alias did not retain the inflated Java object.");
1893+ Require (
1894+ JNIEnv.IsSameObject (imageView.Handle, aliasAs.Handle),
1895+ "Concrete alias JavaAs did not retain the inflated Java object.");
1896+ if (imageView is AppCompatImageButtonAlias) {
1897+ Require (ReferenceEquals (imageView, alias), "JavaCast did not preserve the inflated alias peer.");
1898+ }
1899+ Require (
1900+ AppCompatImageButtonAlias.HandleConstructorCalls == 2,
1901+ "Inflation and concrete alias casts did not use the expected handle constructors.");
1902+
1903+ var tintable = JavaObjectExtensions.JavaCast<ITintableBackgroundView> (untypedImage);
1904+ try {
1905+ using var tintableAs = JavaPeerableExtensions.JavaAs<ITintableBackgroundView> (untypedImage);
1906+ Require (tintable != null, "JavaCast did not resolve the AppCompat interface.");
1907+ Require (tintableAs != null, "JavaAs did not resolve the AppCompat interface.");
1908+ Require (
1909+ JNIEnv.IsSameObject (imageView.Handle, tintable.Handle),
1910+ "Interface cast did not retain the inflated Java object.");
1911+ Require (
1912+ JNIEnv.IsSameObject (imageView.Handle, tintableAs.PeerReference.Handle),
1913+ "Interface JavaAs did not retain the inflated Java object.");
1914+ } finally {
1915+ if (tintable != null && !ReferenceEquals (tintable, imageView)) {
1916+ tintable.Dispose ();
1917+ }
1918+ }
1919+ } finally {
1920+ if (alias != null && !ReferenceEquals (alias, imageView)) {
1921+ alias.Dispose ();
1922+ }
1923+ }
1924+ }
1925+
1926+ void VerifyManagedCreatedViews ()
1927+ {
1928+ using var image = new AppCompatImageButton (this);
1929+ using var untypedImage = new Java.Lang.Object (
1930+ image.Handle,
1931+ JniHandleOwnership.DoNotTransfer | JniHandleOwnership.DoNotRegister);
1932+ var castImage = JavaObjectExtensions.JavaCast<AppCompatImageButton> (untypedImage);
1933+ var imageAs = JavaPeerableExtensions.JavaAs<AppCompatImageButton> (image);
1934+ Require (ReferenceEquals (image, castImage), "Managed-created JavaCast did not preserve peer identity.");
1935+ Require (ReferenceEquals (image, imageAs), "Managed-created JavaAs did not preserve peer identity.");
1936+
1937+ using var alias = new AppCompatImageButtonAlias (this);
1938+ using var untypedAlias = new Java.Lang.Object (
1939+ alias.Handle,
1940+ JniHandleOwnership.DoNotTransfer | JniHandleOwnership.DoNotRegister);
1941+ var castAlias = JavaObjectExtensions.JavaCast<AppCompatImageButtonAlias> (untypedAlias);
1942+ var aliasAs = JavaPeerableExtensions.JavaAs<AppCompatImageButtonAlias> (alias);
1943+ Require (ReferenceEquals (alias, castAlias), "Managed-created alias JavaCast did not preserve peer identity.");
1944+ Require (ReferenceEquals (alias, aliasAs), "Managed-created alias JavaAs did not preserve peer identity.");
1945+ }
1946+
1947+ static void VerifyCallerDirectedGenericWrapper ()
1948+ {
1949+ using var list = new JavaList ();
1950+ using var untyped = new Java.Lang.Object (
1951+ list.Handle,
1952+ JniHandleOwnership.DoNotTransfer | JniHandleOwnership.DoNotRegister);
1953+ using var generic = JavaObjectExtensions.JavaCast<JavaList<string>> (untyped);
1954+ using var genericAs = JavaPeerableExtensions.JavaAs<JavaList<string>> (untyped);
1955+ Require (generic != null, "JavaCast did not create the caller-directed generic wrapper.");
1956+ Require (genericAs != null, "JavaAs did not create the caller-directed generic wrapper.");
1957+ Require (
1958+ JNIEnv.IsSameObject (generic.Handle, genericAs.Handle),
1959+ "Caller-directed generic wrappers did not retain the same Java object.");
1960+ generic.Add ("alias");
1961+ Require (genericAs [0] == "alias", "Caller-directed generic wrappers did not round trip their value.");
1962+ Require (
1963+ ReferenceEquals (generic, JavaPeerableExtensions.JavaAs<JavaList<string>> (generic)),
1964+ "Generic JavaAs did not preserve the typed peer.");
1965+ }
1966+
1967+ static string Describe (Java.Lang.Object value)
1968+ {
1969+ return $"managed={value.GetType ().FullName}, java={JNIEnv.GetClassNameFromInstance (value.Handle)}";
1970+ }
1971+
1972+ static void Require (bool condition, string message)
1973+ {
1974+ if (!condition) {
1975+ throw new InvalidOperationException (message);
1976+ }
1977+ }
1978+ }
1979+
1980+ [Register ("androidx/appcompat/widget/AppCompatImageButton", DoNotGenerateAcw = true)]
1981+ public sealed class AppCompatImageButtonAlias : AppCompatImageButton
1982+ {
1983+ public static int HandleConstructorCalls;
1984+
1985+ public AppCompatImageButtonAlias (Context context)
1986+ : base (context)
1987+ {
1988+ }
1989+
1990+ public AppCompatImageButtonAlias (Context context, IAttributeSet attrs)
1991+ : base (context, attrs)
1992+ {
1993+ }
1994+
1995+ public AppCompatImageButtonAlias (Context context, IAttributeSet attrs, int style)
1996+ : base (context, attrs, style)
1997+ {
1998+ }
1999+
2000+ public AppCompatImageButtonAlias (IntPtr handle, JniHandleOwnership transfer)
2001+ : base (handle, transfer)
2002+ {
2003+ HandleConstructorCalls++;
2004+ }
2005+ }
2006+ }
2007+ """ . Replace (
2008+ "__ALLOW_REGISTERED_ALIAS_INFLATION__" ,
2009+ typemapImplementation == "llvm-ir" ? "true" : "false" ) ;
2010+ using var builder = CreateApkBuilder ( packageName : packageName ) ;
2011+ Assert . AreEqual ( proj . PackageName , TestPackageNames [ packageName ] , "Teardown should track the installed package." ) ;
2012+ RunAdbCommand ( $ "uninstall { proj . PackageName } ") ;
2013+ try {
2014+ Assert . True ( builder . Install ( proj ) , "Project should have installed." ) ;
2015+ ClearAdbLogcat ( ) ;
2016+ RunProjectAndAssert ( proj , builder , doNotCleanupOnUpdate : true ) ;
2017+ Assert . True ( WaitForActivityToStart ( proj . PackageName , "MainActivity" ,
2018+ Path . Combine ( Root , builder . ProjectDirectory , "logcat.log" ) , ActivityStartTimeoutInSeconds ) , "Activity should have started." ) ;
2019+ Assert . True ( MonitorAdbLogcat ( line => line . Contains ( expectedLogcatOutput ) ,
2020+ Path . Combine ( Root , builder . ProjectDirectory , "startup-logcat.log" ) , 45 ) , $ "Output did not contain { expectedLogcatOutput } .") ;
2021+ } finally {
2022+ RunAdbCommand ( $ "shell am force-stop --user all { proj . PackageName } ") ;
2023+ RunAdbCommand ( $ "uninstall { proj . PackageName } ") ;
2024+ }
2025+ }
2026+
17542027 [ Test ]
17552028 public void CheckXamarinFormsAppDeploysAndAButtonWorks ( [ Values ( AndroidRuntime . CoreCLR , AndroidRuntime . NativeAOT ) ] AndroidRuntime runtime )
17562029 {
0 commit comments