Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot send local notification with a ScheduleDate set using 3.2.4 #1406

Open
6 of 10 tasks
munkii opened this issue Feb 21, 2024 · 9 comments · Fixed by #1458
Open
6 of 10 tasks

Cannot send local notification with a ScheduleDate set using 3.2.4 #1406

munkii opened this issue Feb 21, 2024 · 9 comments · Fixed by #1458
Labels
bug Something isn't working

Comments

@munkii
Copy link

munkii commented Feb 21, 2024

Component/Nuget

Notifications (Shiny.Notifications)

What operating system(s) are effected?

  • iOS (13+ supported)
  • Mac Catalyst
  • Android (8+ supported)

Version(s) of Operation Systems

Android 12, 13 and 14 and 9 (Physical devices)

Hosting Model

  • MAUI
  • Native/Classic Xamarin
  • Manual

Steps To Reproduce

Using Shiny 3.2.4 set the ScheduleDate on the local notifcation and try and send it. Notifcation does not appear

  • Previous version 2.7.3 worked fine with Android
  • 3.2.3 works fine with iOS

Expected Behavior

I'd expect to see a scheduled notification or an error.

Permission issues with Android 14 wouldn't be that surprising and I have logged an issue with Xamarin Essentials FWIW

Actual Behavior

No error and no Notification. If I do not set the ScheduleData the notifcation happens immediately without issue

Exception or Log output

No response

Code Sample

var status = await this.scheduleExactNotificationPermission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
    status = await this.scheduleExactNotificationPermission.RequestAsync();
}

AccessState state = await this.notificationManager.RequestAccess(AccessRequestFlags.TimeSensitivity);

if (state == AccessState.Available || status == PermissionStatus.Granted)
{
    var pendingNotificationsOnThisDevice = await this.notificationManager.GetPendingNotifications();

    if (pendingNotificationsOnThisDevice.Any(n => n.Title == title) == false)
    {
        var notification = new Notification()
        {
            Title = title,
            Message = reminderText,
            Channel = "DefaultH",
            ScheduleDate = DateTime.Now.AddMinutes(30),
        };

#if DEBUG
        notification.ScheduleDate = DateTime.Now.AddMinutes(2);
        notification.Message += " DEBUG";
#endif
        System.Diagnostics.Debug.WriteLine("RemindToCallAsync Create Scheduled Notification");

        await this.notificationManager.Send(notification);
    }
    else
    {
        // There is a pending notification to call the Nurse Team. Do not hassle user with another one.
        this.logger.LogInteraction("Pending notification. Do not add another.");
    }
}

AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:usesCleartextTraffic="false" android:versionCode="1" android:versionName="3.7.0" package="com.Us.OurProjectApp" android:installLocation="auto">
	<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" />
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.WAKE_LOCK" />
	<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
	<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.VIBRATE" />
	<uses-permission android:name="android.permission.BLUETOOTH" tools:node="replace" android:maxSdkVersion="30" />
	<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" tools:node="replace" android:maxSdkVersion="30" />
	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
	<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" tools:node="remove" android:maxSdkVersion="30" />
	<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" android:maxSdkVersion="30" />
	<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="replace" tools:targetApi="31" />
	<uses-permission android:name="android.permission.BLUETOOTH_SCAN" tools:node="replace" android:usesPermissionFlags="neverForLocation" tools:targetApi="31" />
	<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
	<application android:label="US RM" android:icon="@mipmap/app_icon" Name="US RM" tools:replace="android:label">
		<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="DefaultH" />
		<activity android:name="microsoft.identity.client.BrowserTabActivity" android:exported="true">
			<intent-filter>
				<action android:name="android.intent.action.VIEW" />
				<category android:name="android.intent.category.DEFAULT" />
				<category android:name="android.intent.category.BROWSABLE" />
				<data android:scheme="msalGUIDHERE" android:host="auth" />
			</intent-filter>
		</activity>
	</application>
	<queries>
		<intent>
			<action android:name="android.support.customtabs.action.CustomTabsService" />
		</intent>
		<intent>
			<action android:name="android.intent.action.SENDTO" />
			<data android:scheme="mailto" />
		</intent>
		<intent>
			<action android:name="android.intent.action.DIAL" />
			<data android:scheme="tel" />
		</intent>
	</queries>
</manifest>

AssemblyInfo Permission attributes

[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.PostNotifications)]
[assembly: UsesPermission(Android.Manifest.Permission.ScheduleExactAlarm)]
[assembly: UsesPermission(Android.Manifest.Permission.UseExactAlarm)]

Code of Conduct

  • I have supplied a reproducible sample that is NOT FROM THE SHINY SAMPLES!
  • I am a Sponsor OR I am using the LATEST stable/beta version from nuget (v3.0 stable - ALPHAS are not taking issues - Sponsors can still send v2 issues)
  • I am Sponsor OR My GitHub account is 30+ days old
  • I understand that if I am checking these boxes and I am not actually following what they are saying, I will be removed from this repository!
@munkii munkii added bug Something isn't working unverified This issue has not been verified by a maintainer labels Feb 21, 2024
@aritchie
Copy link
Member

Can you share your android manifest

@aritchie
Copy link
Member

Previous versions of local notifications used shiny jobs which caused delays. V3 uses the alarm which needs a permission request. Try the extension off INotificationManager called RequestRequiredAccess(Notification notification)

@munkii
Copy link
Author

munkii commented Feb 26, 2024

I tried calling RequestRequiredAccess with a notification that had ScheduleDate set and I got NullReferenceException. I copied the extension method so I could run it locally.

var request = AccessRequestFlags.Notification;
if (notification.RepeatInterval != null)
    request |= AccessRequestFlags.TimeSensitivity;

if (notification.ScheduleDate != null)
{
    var channelId = notification.Channel ?? Channel.Default.Identifier;
    var channel = notificationManager.GetChannel(channelId)!;

    if (channel!.Importance == ChannelImportance.High)
        request |= AccessRequestFlags.TimeSensitivity;
}

if (notification.Geofence != null)
    request |= AccessRequestFlags.LocationAware;

return await this.notificationManager
    .RequestAccess(request)
    .ConfigureAwait(false);

The call to RequestAccess sees the following callback to MainActivity.OnRequestPermissionsResult

  1. requestCode = 4
  2. permissions = ["android.permission.POST_NOTIFICATIONS" "android.permission.SCHEDULE_EXACT_ALARM"]
  3. grantResults = [0 -1]

In one of my debug scenarios the channel had not been created. That throws a NullReferenceException when trying to read channel!.Importance. I do however agree I should not be calling this without checking the channel has been created. I am just pointing out that the method should probably check too.

Once the code runs on a device with the Channel I get an AccessState of Restricted.If I remove the Channel then the AccessRequestFlags.TimeSensitivity flag is not set and I get AccessState of Available.

@aritchie
Copy link
Member

This one is going to require some fiddling. Android 14 (13 a bit) changes the way the alarm permission works.

@aritchie aritchie removed the unverified This issue has not been verified by a maintainer label Feb 28, 2024
@anchorit3
Copy link

anchorit3 commented Apr 21, 2024

Hi, @munkii, @aritchie
I spend some time on investigation on Shiny 3.3.3 / dev branch of source code and MAUI but behavior should be really similar also for Xamarin and my conclusions are that there is couple things:

  1. in manifest probably should be max api 33 for SCHEDULE_EXACT_ALARM (now with Shiny.Templates is set to 32)
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="33" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
  1. for api 34 we should check permissions on different way like android documentation suggest + request user to open special access settings in case when permission was not granted yet (on api 34 default is not granted, unfortunately I don't have good idea how to implement it in library)
 if (!this.Alarms.CanScheduleExactAlarms())
{
    var intent = new Android.Content.Intent(Android.Provider.Settings.ActionRequestScheduleExactAlarm);
    Application.Current.Context.StartActivity(intent);
}
  1. implementation of Send(Notification notification) in Platforms/Android/NotificationManager.cs code.
    After scheduling notification I checked pending notifications and was always 0 on android:
 var list = await notificationManager.GetPendingNotifications();
 var pendingNotificationsCount = list.Count(); // result is 0, but I scheduled notifications in future :(

so I checked implementation of GetPendingNotifications() which is:

public Task<IList<Notification>> GetPendingNotifications()
       => Task.FromResult((IList<Notification>)this.repository.GetList<AndroidNotification>().OfType<Notification>().ToList());

then if pending notifications are coming from repository I checked place where potentially AndroidNotification should be stored in repository which is Send(Notification notification):

public async Task Send(Notification notification)
{
    notification.AssertValid();
    var android = notification.TryToNative<AndroidNotification>();

    (...)

    if (notification.ScheduleDate == null && notification.Geofence == null)
    {
        var native = builder.Build();
        this.manager.NativeManager.Notify(notification.Id, native);
    }
    else
    {
        // ensure a channel is set
        notification.Channel = channel!.Identifier;
        this.repository.Set(notification);

        if (notification.ScheduleDate != null)
            this.manager.SetAlarm(notification);
    }
}

then we can see that into repository we store generic Notification type instead of AndroidNotification so I stored it as well. I added after this.repository.Set(notification); :

//re-asign android variable because we did manipulation from the moment when it was defined
android = notification.TryToNative<AndroidNotification>();
this.repository.Set(android);

and magic just happened - Scheduled Notifications started to works!

I'm not sure how it should be, maybe this.repository.Set(notification) should be removed or maybe not or maybe something else should be changed in other files (I dont know how the implementation of notifications looks like in other files and if generic type need to be stored or no or maybe AndroidNotificationProcessor is looking for wrong stuff in repository).

However I hope it can help you to understand better problem and solve it in plugin :)

@aritchie
Copy link
Member

@anchorit3 while I appreciate the effort. Most of the work is done in the 4.0 branch. The issue isn't really with the "making it happen", but with how much of a mess the permissions have gotten now due to this additional permission. I'm working on that part, but the plugin will do all of the permissions properly in the future.

@munkii
Copy link
Author

munkii commented Jun 3, 2024

This is still an issue for us even when using 3.3.3.

I can ask the Android AlarmService if i can schedule exact alarms via the canScheduleExactAlarmsMethod and that returns true.

AlarmManager alarmManager = (AlarmManager)GetSystemService(AlarmService);
Method canScheduleExactAlarmsMethod = alarmManager.Class.GetMethod("canScheduleExactAlarms");
bool canScheduleExactAlarms = (bool)canScheduleExactAlarmsMethod.Invoke(alarmManager);

However when it comes time to send the notification and I call Shiny I get AccessState.Restricted

var result = await this.remindersService.CheckNotificationsPermission(AccessRequestFlags.Notification | AccessRequestFlags.TimeSensitivity);

if (result != Shiny.AccessState.Available)
{
    this.notificationPermissionRequestedAndDenied = true;
}

public async Task<Shiny.AccessState> CheckNotificationsPermission(AccessRequestFlags accessRequestFlags = AccessRequestFlags.Notification)
{
    return await this.notificationManager.RequestAccess(accessRequestFlags);
}

I looked at the implmentation of NotifcationManager RequestAccess and had a look at starting the Android.Provider.Settings.ActionRequestScheduleExactAlarm activity myself. Whilst it does start it gives me as the user now way to add us to the allowed set of apps.

Are these exact notifications best left alone for now. Should wait for 4.0? Will 4.0 be MAUI only?

@aritchie
Copy link
Member

aritchie commented Jun 3, 2024

It's still an issue because I haven't fixed it. I'll get to this when I can, but it isn't a 30 second fix.

Fixes at this point are set for 4.0 which will be .NET8+ only

@elvenprogrammer

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants