ImageReader.SetOnImageAvailableLister throws cast error unless IOnImageAvailableListener is implemented by Activity

607 Views Asked by At

I'm trying to encapsulate taking a screen shot of the Android screen away from my primary activity. I have a class that implements

ImageReader.IOnImageAvailableListner

but

 imageReader.SetOnImageAvailableListener(this, null)

in the class throws a cast exception (but not a compile exception). The only way I've found to avoid this is to have my activity itself implement IOnImageAvailableListner. It seems that there is some part of Xamarin/Mono that really requires the argument to SetOnImageAvailableListner to be of type Activity that implements IOnImageAvailableListner.

here is the relevant section of my class:

    public class Screenshooter : ImageReader.IOnImageAvailableListener
    {
        public void TakeScreenshot(Context context,
                                            Result resultCode,
                                            Intent data,
                                            IOnScreenshot onScreenshotCallback)
        {
            _context = context;
            _onScreenshot = onScreenshotCallback;

            _imageAvailableCount = 0;

            var size = new Point();
            ((Activity) context).WindowManager.DefaultDisplay.GetSize(size);
            _width = size.X;
            _height = size.Y;
            _imageReader = ImageReader.NewInstance(_width, _height, ImageFormatType.Rgb565, 2);
            MediaProjectionManager mediaProjectionManager  = (MediaProjectionManager) context.GetSystemService(Context.MediaProjectionService);

            if (_mediaProjection == null)
            {
                _mediaProjection =mediaProjectionManager.GetMediaProjection((int) resultCode, data);
                if (_mediaProjection == null)
                {
                    //                    Log.e(TAG, "MediaProjection null. Cannot take the screenshot.");
                    logger.Error("MediaProjection null. Cannot take the screenshot.");
                    return;
                }
            }
            try
            {
                _virtualDisplay = _mediaProjection.CreateVirtualDisplay("Screenshotter", _width, _height, (int) context .Resources.DisplayMetrics.DensityDpi, (DisplayFlags) DisplayManager.VirtualDisplayFlagAutoMirror, _imageReader.Surface, null, null);

                /////////////   THIS IS THE LINE THAT FAILS    ////////////
                _imageReader.SetOnImageAvailableListener(this, null);
                ///////////////////////////////////////////////////////////
            }
            catch (Exception ex)
            {
                logger.Error($"Error in {nameof(TakeScreenshot)}", ex);
                throw;
            }

            return;
        }
        //IOnImageAvailableListener Members

        public void OnImageAvailable(ImageReader reader)
        {
               // do stuff
         }

...
   }

I instantiate it in the activity that requests the screen shot with:

public class MainActivity : Activity
{
    private void TakeScreenshot()
    {
        StartActivityForResult(((MediaProjectionManager) GetSystemService(MediaProjectionService)).CreateScreenCaptureIntent(),
                               REQUEST_CODE);
    }

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
 {
     Screenshooter.GetInstance().takeScreenShot(this, resultCode, data, callback);
}

The callback code that is called after the screenshot is completed is omitted.

I'm seeing Java android code examples of this pattern everywhere. (specifically, I've been looking at this example: https://github.com/omerjerk/Screenshotter)

I'm new to both Android and Xamarin, but have extensive experience in C#.

Is this a bug in Xamarin? Is there a work-around?

Thanks

1

There are 1 best solutions below

1
On

The only way I've found to avoid this is to have my activity itself implement IOnImageAvailableListner.

No, it's not the only way, to avoid implement the interface on activity class, you're doing right to create your own class which implements this interface, for example:

public class MyImageReaderListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener
{
    public void Dispose()
    {
        //TODO:
    }

    public void OnImageAvailable(ImageReader reader)
    {
        //TODO:
    }
}

The cast exception is caused by the use of this, which refers to the Activity class, and this class doesn't implement the interface. If you're familiar with C#, you should be able to understand this, you may change this here to a new instance of your class:

imagereader.SetOnImageAvailableListener(new MyImageReaderListener(), null);

For code like:

Screenshotter.getInstance()
             .setSize(720, 1280)
             .takeScreenshot(this, resultCode, data, new ScreenshotCallback() {
                 @Override
                 public void onScreenshot(Bitmap bitmap) {
                     //Enjoy your bitmap
                 }
             });

C# don't have such anonymous inner class of java, you may refer to Can a C# anonymous class implement an interface?. That's why we created a class to implement the interface.

The reason why a lot of demos directly implement the interface on activity is that sometimes we need a return value from the listener to continue our logical work, you may check the Api design of Xamarin for Events and Listeners.