Forum Xamarin.Android

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Using Android API methods and interfaces marked with @hide - specifically setDelayer(Filter.Delayer)

NickTalbotNickTalbot USMember
edited June 2013 in Xamarin.Android

Hi, I would like to use the setDelayer() method of Filter in Android, as seen here http://www.androidjavadoc.com/2.3/android/widget/Filter.html and https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/Filter.java

(It looks ideal - I want to wait until the user stops typing before executing an expensive filter of an AutoCompleteTextView)

However, neither setDelayer() or the nested Delayer interface are accessible in the Filter class in Xamarin Android. They also aren't present in the official Android docs http://developer.android.com/reference/android/widget/Filter.html

I can see this is because both are marked with @hide in the source.

My questiion is - how do I access these methods and types in Xamarin? Could I access them via C# reflection, are they accessible by reflection, or is there another 'better' way to access them?

Thinking about it further, even if reflection works for obtaining and invoking Filter.SetDelayer, it doesn't help for implementing a class based on the Delayer interface - I need access to the Delayer interface at compile time so I can derive from it.

Unless - do I have to get involved in Reflection.Emit to build a class based on Delayer at runtime?? (Will that work?) Or do I have to import the Delayer interface using Java and a wrapped library in some fashion?

Please help!

Many thanks,
Nick Talbot

Best Answer

Answers

  • gregkogregko USMember
    edited June 2013

    Hi Nick,

    From the top of my head, without digging into the details, it would be easiest to write a wrapper class around what you need in Java, access the hidden members with Java reflection, make a Jar from your own Java code and use in C++ project normally.

    However, using a hidden method in general is not a good idea, it may be e.g. removed in a future version of Android, or changed unexpectedly. Your Java code should be prepared for all of this, handle exceptions when the method is not present or has a different signature, and then your C# code should be aware that the calls may fail and deal with such situation gracefully.

    Greg

  • NickTalbotNickTalbot USMember

    The thing is, the Java is already wrapped by the Xamarin C# API - it's just that the Xamarin API doesn't provide access to Android items that are marked with @hide. I've found the Xamarin reference now, see http://androidapi.xamarin.com/?link=T:Android.Widget.Filter - no mention of the @hide item.

    I hear what you're saying about using hidden items that might disappear, although in this case I think that's unlikely. According to https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/Filter.java setDelayer() has been in the Android since 2007/Version 1! I do wonder why it's hidden though.

    I'm not at my own PC right now, I'll try the reflection tonight, although it might not work if C# reflection requests aren't forwarded into Java reflection requests by Mono Android.

    http://www.androidjavadoc.com/2.3/android/widget/Filter.Delayer.html is Android interface that needs to be implemented - IF the C# reflection provides access to this, I could use Reflection.Emit to construct a class at runtime that inherits from it and implements it.

    All this to use the 'in-built' Android 'wait until stop typing' before filtering a list!?!

    Hopefully Xamarin support will pick this up later today.

    Nick

  • NickTalbotNickTalbot USMember
    edited June 2013

    Further investigation shows that the Filter Delayer is used internally by Android for exactly the purpose I want to. The Android Search Dialog http://developer.android.com/guide/topics/search/search-dialog.html uses another @hide class SuggestionsAdapter http://www.java2s.com/Open-Source/Android/android-core/platform-frameworks-base/android/app/SuggestionsAdapter.java.htm which uses the Filter.Delayer to wait 1/2 second before filtering.

    Is this approach possible in Xamarin Android C#? If I was developing in Java, I woudl be able to use direct reflection to get the member, as seen in example code here http://www.bravenewgeek.com/bluetooth-blues/

    Please help Xamarin!

  • NickTalbotNickTalbot USMember

    Okay, I've got a bit further along myself.

    If I create a C# class derived from Filter, i.e.

    public class MyFilter : Filter

    and within its constructor do:

    var members = this.GetType ().GetMembers ();

    Then members only contains the 'official' members as expected.

    However:

    var temp2 = this.Class.GetMethods (); // We are derived from C# Java.Lang.Object

    then:

    [12] {public void android.widget.Filter.setDelayer(android.widget.Filter.Delayer)} Java.Lang.Reflect.Method

    is one of the 17 'direct Java object' members, even though it is hidden.

    I still have the problem of building a Delayer-derived C# class though.

  • NickTalbotNickTalbot USMember

    Furthermore,

    var intf = Class.GetMethods ().Single (m => m.Name == "setDelayer").GetParameterTypes ().Single ();

    gets a object with .Name of

    Name "android.widget.Filter$Delayer" string

    So I can 'get' the hidden interface - but again, how do I inherit from this in C#?

  • NickTalbotNickTalbot USMember
    edited June 2013

    Here's my attempt to turn the hidden interface into a C# version using similar code from the Assembly Browser:

    using System;
    using Java.Lang;
    using Android.Runtime;

    namespace Android.Widget
    {
    [Register ("android/widget/Filter$Delayer")]
    public interface IDelayer : IJavaObject, IDisposable
    {
    [Register ("getPostingDelay")]
    long GetPostingDelay(ICharSequence constraint);
    }
    }

    It doesn't build though - I get error:

    Error XA4209: Failed to create JavaTypeInfo for class: SpeedyRoute.PlacesDelayer due to System.NullReferenceException: Object reference not set to an instance of an object
    at Xamarin.Android.Tasks.JavaTypeInfo+Signature..ctor (System.String name, System.String signature, System.String connector, System.String managedParameters, System.String outerType, System.String superCall) [0x00000] in :0
    at Xamarin.Android.Tasks.JavaTypeInfo+Signature..ctor (Mono.Cecil.MethodDefinition method, Android.Runtime.RegisterAttribute register, System.String managedParameters, System.String outerType) [0x00000] in :0
    at Xamarin.Android.Tasks.JavaTypeInfo+Signature..ctor (Mono.Cecil.MethodDefinition method, Android.Runtime.RegisterAttribute register) [0x00000] in :0
    at Xamarin.Android.Tasks.JavaTypeInfo.AddMethod (Mono.Cecil.MethodDefinition registeredMethod, Mono.Cecil.MethodDefinition implementedMethod) [0x00000] in :0
    at Xamarin.Android.Tasks.JavaTypeInfo..ctor (Mono.Cecil.TypeDefinition type, System.String outerType) [0x00000] in :0
    at Xamarin.Android.Tasks.JavaTypeInfo..ctor (Mono.Cecil.TypeDefinition type) [0x00000] in :0
    at Xamarin.Android.Tasks.Generator.GenerateJavaSource (Mono.Cecil.TypeDefinition t, System.String outputPath, Boolean useSharedRuntime, Boolean hasExportReference) [0x00000] in :0 (XA4209) (SpeedyRoute)

  • NickTalbotNickTalbot USMember
    edited June 2013

    Hmm, I can see after reading http://docs.xamarin.com/guides/android/advanced_topics/java_integration_overview/working_with_jni and https://github.com/xamarin/monodroid-samples/blob/master/SanityTests/ManagedAdder.cs#L88 that I have some more work to do - I need to implement a IDelayerInvoker class in C# to be referenced by the IDelayer [Register] attribute I think. I'll have another crack at it tonight.

  • NickTalbotNickTalbot USMember

    Ahh man, I'm almost there, but have fallen at the very last fence! I re-read the excellent http://docs.xamarin.com/guides/android/advanced_topics/java_integration_overview/working_with_jni guide, as this does actually have all the examples necessary. I have adapted the examples, and got to the point where all my C# now compiles, and the Xamarin/MonoDroid build process generates Java underneath, and then that fails because it slightly miss-names what should be being built against.

    First, I added a C# interface [Register]ed alias for the un-mapped Java Delayer interface, with the correct method JNI Type Signatures:

    using System;
    using Java.Lang;
    using Android.Runtime;
    
    namespace Android.Widget
    {
        [Register ("android/widget/Filter$Delayer", DoNotGenerateAcw=true)]
        public interface IDelayer : IJavaObject, IDisposable
        {
            [Register ("getPostingDelay", "(Ljava/lang/CharSequence;)J", "GetPostingDelayHandler:SpeedyRoute.IDelayerInvoker, SpeedyRoute")]
            long GetPostingDelay(ICharSequence constraint);
        }
    }
    

    I then added another C# class to implement the required interface invoker:

    using System;
    using Android.Widget;
    using Android.Runtime;
    using Java.Lang;
    
    namespace SpeedyRoute
    {
        public class IDelayerInvoker : Java.Lang.Object, IDelayer
        {
            IntPtr class_ref;
    
            public IDelayerInvoker (IntPtr handle, JniHandleOwnership transfer)
                : base (handle, transfer)
            {
                IntPtr lref = JNIEnv.GetObjectClass (Handle);
                class_ref = JNIEnv.NewGlobalRef (lref);
                JNIEnv.DeleteLocalRef (lref);
            }
    
            protected override void Dispose (bool disposing)
            {
                if (this.class_ref != IntPtr.Zero)
                    JNIEnv.DeleteGlobalRef (this.class_ref);
                this.class_ref = IntPtr.Zero;
                base.Dispose (disposing);
            }
    
            protected override Type ThresholdType {
                get {return typeof (IDelayerInvoker);}
            }
    
            protected override IntPtr ThresholdClass {
                get {return class_ref;}
            }
    
            public static IDelayerInvoker GetObject (IntPtr handle, JniHandleOwnership transfer)
            {
                return new IDelayerInvoker (handle, transfer);
            }
    
            #region GetPostingDelay
            IntPtr id_getPostingDelay;
            public long GetPostingDelay (ICharSequence constraint)
            {
                if (id_getPostingDelay == IntPtr.Zero)
                    id_getPostingDelay = JNIEnv.GetMethodID (class_ref, "getPostingDelay", "(Ljava/lang/CharSequence;)J");
                return JNIEnv.CallLongMethod (Handle, id_getPostingDelay, new JValue (constraint));
            }
    
            #pragma warning disable 0169
            static Delegate cb_getPostingDelay;
            static Delegate GetPostingDelayHandler ()
            {
                if (cb_getPostingDelay == null)
                    cb_getPostingDelay = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, IJavaObject, long>) n_GetPostingDelay);
                return cb_getPostingDelay;
            }
    
            static long n_GetPostingDelay (IntPtr jnienv, IntPtr lrefThis, IJavaObject constraint)
            {
                IDelayer __this = Java.Lang.Object.GetObject<IDelayer>(lrefThis, JniHandleOwnership.DoNotTransfer);
    
                return __this.GetPostingDelay (constraint as ICharSequence);
            }
            #pragma warning restore 0169
            #endregion
        }
    }
    

    I then attempted to build the solution/project. The build process seems to compile everything, and also generates a java file IDelayerInvoker.java, partially shown below:

    package speedyroute;
    
    
    public class IDelayerInvoker
        extends java.lang.Object
        implements
            mono.android.IGCUserPeer,
            android.widget.Filter.Delayer
    {
        static final String __md_methods;
    

    A build error is reported 'Cannot find symbol' for

    android.widget.Filter.Delayer
    

    As we know, the Android SDK Delayer interface is a @hide hidden interface nested inside android.widget.Filter

    I'm guessing that either it should be marked in the generated code as:

    android.widget.Filter$Delayer
    

    or, maybe even more likely, the 'Cannot find symbol' error is related to the fact that the Delayer interface is not present in android.jar, as it is a hidden interface. But I'm guessing at this point.

    Time to invoke my Xamarin support request as a paid-up licensed user! Help!

    Regards,
    Nick Talbot

  • StevenTheEvenStevenTheEven SEMember ✭✭✭

    Then you should send them an e-mail because that's the way you will get support. You might get it here but to be sure, send mail.

  • AtsushiEnomotoAtsushiEnomoto JPMember, Xamarin Team Xamurai

    Hello,

    You can't compile any code that uses Filter.setDelayer() IN ANY LANGUAGE, right? The question sounds like "how can I access to invisible methods in my code publicly?" and the would be like "no way".

    Sorry but we cannot offer any way that the standard Java toolchain does not allow users to compile. I didn't find any use of that method in Java on the web, so I think no one in Java Android ecosystem was trying to use it either way.

  • NickTalbotNickTalbot USMember

    Hi eno, I actually got a little bit closer, to the point where almost everything compiles, but still not quite there.

    I followed the instructions here https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-2-hacking-around/ which detail how to create an android.jar with the missing hidden items that are available at runtim/through reflection made to be available at compile time.

    I confirmed I'm building in Xamarin against Android API Level 11 - if I rename the android.jar in my android SDK for API 11, Xamarin won't build anything. I then copied in the original's place a 'custom/full' android.jar for Level 11 in place using the above link's instructions.

    The code now seems to compile, but I get a weird resource build error "Tool exited with code:1 Output obj/Debug/res/layout/main.xml:1 error: No resource identifier found for attribute 'orientation' in package 'android'"

    I have now restored the original android.jar to get the 'can't find android.widget.filter.delayer' message back, and commented out the IDelayer related C# code.

    Again, I can easily access the Filter.setDelayer() method through reflection, but it requires an implementation of the Delayer interface in code at compile time, so it isn't possible without the modified android.jar.

    Any idea about the weird resource error? It all goes away when I go back to the standard android.jar.

    Guess I'll just have to use a timer to check how long its been before a request comes in before performing the filter I suppose.

    None of this is Xamarin's fault - Xamarin is a fantastic tool - it's android which I don't understand why this setDelayer method isn't public.

  • AtsushiEnomotoAtsushiEnomoto JPMember, Xamarin Team Xamurai

    Tiny correction: actually the blog post was written in January, so even before Honeycomb binary release! :-)

  • NickTalbotNickTalbot USMember

    Hi Eno, thanks for your comments, you're right, it is a bit of a tricky hack. I've had a change of heart, and stepped back to solve my problem in another away. I've junked all the tricky stuff mentioned earlier, along with Android Filters etc, and gone now for a simpler implementation by overriding OnTextChanged, and using the excellent Xamarin beta-support for .NET 4.5 / C#5 async/await support. My code has shrank to a quarter of the size, and now works perfectly without any tricky hacks. Better to play to Xamarin's strength's and the great C# support. Thanks again though!

Sign In or Register to comment.