ExposureNotification.Sample icon indicating copy to clipboard operation
ExposureNotification.Sample copied to clipboard

Android: StartAsync resolves too fast

Open nc-kano opened this issue 4 years ago • 10 comments

When we run StartAsync method with bluetooth OFF it correctly shows us a dialog and after clicking ok it turns the BT on. However if we check status right after that with GetStatusAsync() it returns BluetoothOff as adapter haven't manage it to start fast enough. Because of that we need to start again so we see the dialog for the second time. I think the StartAsync should wait until adapter will be enabled and then resolve.

Issue observed on Xiaomi Redmi Note 8T with Android 9 and Huawei Mate 20 lite with Android 10

nc-kano avatar Jun 02 '20 15:06 nc-kano

Update: I have investigated it further and I have found a differences in start method for Xamarin and android: android solution:

start()
        .addOnSuccessListener(
            unused -> {
            })
        .addOnFailureListener(
            exception -> {
            })
       .addOnCanceledListener(() -> {});

Xamarin: await StartAsync(); // returns only empty Task

So the android solution gives us more information here.

Also I have found a workaround for this issue: If we will create a BroadcastReceiver for bluetooth adapter ("android.bluetooth.adapter.action.STATE_CHANGED") and await for the bluetooth to turn on in OnActivityResult method before we will call ExposureNotification.OnActivityResult(requestCode, resultCode, data) it will work correctly.

nc-kano avatar Jun 22 '20 13:06 nc-kano

My simplified workaround:

        public static TaskCompletionSource<bool> AgBluetoothTaskCompletionSource;

        public async void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            if (requestCode != EnApiRequestCode || resultCode != Result.Ok) return;
            AgBluetoothTaskCompletionSource = new TaskCompletionSource<bool>();
            if (BluetoothAdapter.DefaultAdapter != null
                && BluetoothAdapter.DefaultAdapter.IsEnabled)
            {
                AgBluetoothTaskCompletionSource.TrySetResult(true);
            }
            // Set timeout
            Task.Run(async () =>
            {
                await Task.Delay(Timeout);
                AgBluetoothTaskCompletionSource?.TrySetResult(BluetoothAdapter.DefaultAdapter != null
                                                              && BluetoothAdapter.DefaultAdapter.IsEnabled);
            });

            await AgBluetoothTaskCompletionSource.Task;

            ExposureNotification.OnActivityResult(requestCode, resultCode, data);
        }

where AgBluetoothTaskCompletionSource is set to true in broadcast receiver when bluetooth is ON

nc-kano avatar Jun 22 '20 13:06 nc-kano

@nc-kano I looked into this and I am not sure exactly why yours is returning too soon. Have you tested the code on Android and confirmed that it does actually wait?

Looking at our code, we also wait the Android task: https://github.com/xamarin/XamarinComponents/blob/master/XPlat/ExposureNotification/source/Xamarin.ExposureNotification/ExposureNotification.android.cs#L100

The chain from native code to the xplat is basically: start the Android task, convert it to a .NET task, await the .NET task. The only way for the .NET task to finish is to wait for the Android task to complete.

I'll do some tests with my device and see exactly what is happening. Maybe we missed something, maybe the Android SDK has an issue.

mattleibow avatar Jun 22 '20 17:06 mattleibow

@mattleibow Thank you for the response. Yes, I have tested it. Until now it was tested on a wide variety of devices and on all it behaves in the similar way.

Our starting method is more or less like this:

        public async Task<bool> StartBluetooth()
        {
            try
            {
                await Xamarin.ExposureNotifications.ExposureNotification.StartAsync();
            }
            catch (Exception e)
            {
                // Error handling
            }
            return (await Xamarin.ExposureNotifications.ExposureNotification.GetStatusAsync() == Status.Active);
        }

And it always return false if bluetooth was turned off.

nc-kano avatar Jun 22 '20 19:06 nc-kano

I hope you have time to look at this soon. We have this issue in production with half a million users.

JanettHolst290490 avatar Jul 09 '20 12:07 JanettHolst290490

Hi. So what is the general consensus on this app/library. Is it now stable enough to use in production?

I've been holding off on implementation until the debugging/refactoring has settled down, and it seems to have at this point.

Anyone using this in production?

dlandi avatar Jul 18 '20 18:07 dlandi

@dlandi yes, we are using it in Denmark. Despite this issue with starting API is working fine.

nc-kano avatar Aug 06 '20 07:08 nc-kano

@mattleibow xamarin sample application does not work at all as it is even not on a list of covid tracking apps. it is installed and enabled but android does not recognize it as covid tracking app and when we enable exposure notifications in the app there is no system dialog: image so it cannot be treated as a reference for starting of the app.

nc-kano avatar Aug 06 '20 07:08 nc-kano

@mattleibow Thank you for the response. Yes, I have tested it. Until now it was tested on a wide variety of devices and on all it behaves in the similar way.

Our starting method is more or less like this:

        public async Task<bool> StartBluetooth()
        {
            try
            {
                await Xamarin.ExposureNotifications.ExposureNotification.StartAsync();
            }
            catch (Exception e)
            {
                // Error handling
            }
            return (await Xamarin.ExposureNotifications.ExposureNotification.GetStatusAsync() == Status.Active);
        }

And it always return false if bluetooth was turned off.

I have try this on Samsung A20 SC-02M and it doenot start the Exposure notification API

cris-m avatar Aug 06 '20 15:08 cris-m

So do you confirm that there is a bug? No it will not start the EN Api before you accept the permission popup.

We would expect StartAsync to not resolve the task before it is in fact started and GetStatusAsync will return Status.Active.

JanettHolst290490 avatar Aug 10 '20 06:08 JanettHolst290490