Essentials: [Question][Bug] Extending Permissions always returns "Granted"

Created on 18 Jul 2020  路  7Comments  路  Source: xamarin/Essentials

Description

It is related to #949. I followed the docs on how extends permissions
(https://docs.microsoft.com/en-us/xamarin/essentials/permissions?context=xamarin%2Fandroid&tabs=android#extending-permissions), but when I execute "CheckStatusAsync" always returns "Granted", and that is not the actual case. My intention is to grant permissions to Bluetooth LE. If I try with a permission included in Essentials all work great. I don't know if it is a bug or I am doing something wrong. Thanks

Steps to Reproduce

  1. I have created the next class on my shared project (Xamarin.Forms):
public partial class BLEPermission : Permissions.BasePlatformPermission
    {

    }
  1. And these codes on Android and iOS projects:
public partial class BLEPermission : Permissions.BasePlatformPermission
    {
        public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
            new (string, bool)[]
            {
                (Manifest.Permission.AccessCoarseLocation, true),
                (Manifest.Permission.AccessFineLocation, true),
                (Manifest.Permission.Bluetooth, true),
                (Manifest.Permission.BluetoothAdmin, true)
            };
    }
public partial class BLEPermission : Permissions.BasePlatformPermission
    {
        protected override Func<IEnumerable<string>> RequiredInfoPlistKeys =>
                () => new string[] { "NSBluetoothAlwaysUsageDescription" };
    }



md5-a7ba177b4b568d3cc14d0e7a04f38998



public async Task<PermissionStatus> CheckAndRequestPermission()
        {
            var status = await Permissions.CheckStatusAsync<BLEPermission>();
            if (status != PermissionStatus.Granted)
            {
                status = await Permissions.RequestAsync<BLEPermission>();
            }

            // Additionally could prompt the user to turn on in settings

            return status;
        }

Expected Behavior

When I open the page that is associated with my ViewModel the OS shall ask the users to accept or deny the permissions.

Actual Behavior

The page loads and nothing happens. When debugging "status" is "Granted".

Basic Information

  • Version with issue: 1.5.3.2
  • Last known good version: -
  • IDE: Visual Studio 2019 16.7 preview 4
  • Platform Target Frameworks: Xamarin.Forms (netstandard2.0)

    • iOS: 13.0

    • Android: 5.0 -> 10.0

  • Nuget Packages: Plugin.BLE, System.Text.Json, Xamarin.Forms, Xamarin.Essentials
  • Affected Devices: Android and iOS

Most helpful comment

I am updating the documentation now :)

All 7 comments

I will have to try out the Android permissions.
You must define them in your Android Manifest. Bluetooth on Android is not a runtime permission. It is only in the manifest which is why it is returning granted -> https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH

For iOS you would actually have to override the check and request to request the permission manually.

@jamesmontemagno thank you for your reply. I had defined them in my Android Manifest and iOS Info.plist, but debugging I noticed that if I add a breakpoint in partial classes it is not called.
I added this code to check on iOS, but I can't test if it works since the code is not called.

public partial class BLEPermission : Permissions.BasePlatformPermission
    {
        protected override Func<IEnumerable<string>> RequiredInfoPlistKeys =>
                () => new string[] { "NSBluetoothAlwaysUsageDescription" };

        public override Task<PermissionStatus> CheckStatusAsync()
        {
            EnsureDeclared();

            return Task.FromResult(CheckPermissionsStatus());
        }

        public override async Task<PermissionStatus> RequestAsync()
        {
            EnsureDeclared();            

            var status = CheckPermissionsStatus();
            if (status == PermissionStatus.Granted)
                return status;

            EnsureMainThread();

            return await RequestPermissionAsync();
        }

        internal void EnsureMainThread()
        {
            if (!MainThread.IsMainThread)
                throw new PermissionException("Permission request must be invoked on main thread.");
        }

        internal PermissionStatus CheckPermissionsStatus()
        {
            var status = CBManager.Authorization;
            return status switch
            {
                CBManagerAuthorization.AllowedAlways => PermissionStatus.Granted,
                CBManagerAuthorization.Denied => PermissionStatus.Denied,
                CBManagerAuthorization.Restricted => PermissionStatus.Restricted,
                _ => PermissionStatus.Unknown,
            };
        }

        internal async Task<PermissionStatus> RequestPermissionAsync()
        {
            try
            {
                using var CBCentralManager = new CBCentralManager();
                return CheckPermissionsStatus();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Unable to get Bluetooth LE permission: " + ex);
                return PermissionStatus.Unknown;
            }
        }
    }

CheckStatusAsync never is called and somehow Permissions.CheckStatusAsync<BLEPermission>(); returns "Granted".
I am exhausting trying and reading the source code of Xamarin Essetials, I am new with the use of partial classes but this is frustrating.

P.S.: I had to copy "EnsureMainThread()" from "Permissions.ios.tvos.watchos.cs" since it is internal and not public.

Do you have a sample project I can test out?

@jamesmontemagno I've uploaded sample project: https://github.com/OrihuelaConde/XEssentialsTest
Since it is my first time working with partial classes I've read that the namespace should be the same of the original class. I've tried with Xamarin.Essentials as namespace of the partial classes and with the namespace of my projects, but neither way worked.
The code that execute the permission check is in "ItemsViewModel.cs" in the method "ExecuteLoadItemsCommand". You can run the app and go to the "Browse" tab to execute it. Thanks!

image

I will give it a try :) thanks for the project :)

I see, yes I documented this wrong. You must call the code from your Android or iOS project.

Partial classes can not span multiple assemblies. Best would be to create an interface:

    public interface IBLEPermission
    {
        Task<PermissionStatus> CheckStatusAsync();
        Task<PermissionStatus> RequestAsync();
    }

Then inherit from this in your iOS/Android projects:

public class BLEPermission : Permissions.BasePlatformPermission, IBLEPermission

BasePlatformpermission implements them already, so good to go there :)

Register in your iOS/Android project startup;

DependencyService.Register<IBLEPermission, BLEPermission>();
            LoadApplication(new App());

Use it:

                var ble = DependencyService.Get<IPermission>();
                var status = await ble.CheckStatusAsync();
                if (status != PermissionStatus.Granted)
                {
                    status = await ble.RequestAsync();
                }

here is a working sample ->
XEssentialsTest.zip

I am updating the documentation now :)

Was this page helpful?
0 / 5 - 0 ratings