Android Code Snippet NB6 class - additional functions

wes58

Active Member
Licensed User
I have been using Barx's great notification builder library for a while. But now, I thought that maybe I should check current Notifications API with Channels and Erel's class NB6.
I noticed that Google removed some methods and that some things that I used before are not
implemented in the NB6 class.
Therefore, I have decided to add some of them.

1. First, I needed an InboxStyle Notification. A notification where you can add a list of 6 lines of text (I saw mention of max. 5 lines and somewhere else 6 lines)

Here is the code to be added to NB6 class:
B4X:
'Creates a "Inbox" notification.
Public Sub InboxStyle(SummaryText As Object, Text As List) As NB6
 If IsBuilder Then

  Dim style As JavaObject
  style.InitializeNewInstance("android.app.Notification$InboxStyle", Null)
  style.RunMethod("setSummaryText", Array(SummaryText))
  Dim i As Int
  For i = 0 To (Min(5, Text.size-1)) 'max. number of lines is set to 6 (from 0 to 5)
   style.RunMethod("addLine", Array(Text.Get(i)))
  Next
  NotificationBuilder.RunMethod("setStyle", Array(style))
 End If
Return Me
End Sub
2 Use Chronometer
Show the Notification#when field as a stopwatch. Instead of presenting "when" as a timestamp, the notification will show an automatically updating display of the minutes and seconds since when. Useful when showing an elapsed time (like an ongoing phone call).
Here is the code to be added to NB6 class:
B4X:
'Show the stopwatch.
'Instead of presenting when as a timestamp,
'the notification will show an automatically updating display of the minutes and seconds
Public Sub UsesChronometer(useChronometer As Boolean) As NB6
 If IsBuilder Then
  NotificationBuilder.RunMethod("setShowWhen", Array(True))
  NotificationBuilder.RunMethod("setUsesChronometer", Array(useChronometer))
 End If
 Return Me
End Sub
3. TimeoutAfter
Specifies a duration in milliseconds after which this notification should be canceled, if it is not already canceled.
Here is the code to be added to NB6 class:
B4X:
'Specifies a duration in milliseconds after which this notification should be canceled, if it is not already canceled.
'Used in SDK > 25 only
Public Sub TimeoutAfter(durationMs As Long) As NB6
 If IsChannel Then
  NotificationBuilder.RunMethod("setTimeoutAfter", Array(durationMs))
 End If
 Return Me
End Sub
4. OnGoing
Set whether this is an "ongoing" notification. Ongoing notifications cannot be dismissed by the user, so your application or service must take care of canceling them.
Here is the code to be added to NB6 class:
B4X:
Public Sub OnGoing(On_going As Boolean) As NB6
 If IsBuilder Then
  NotificationBuilder.RunMethod("setOngoing", Array(On_going))
 End If
 Return Me
End Sub
5. CustomVibrate
Vibrate with a given pattern.
Pass in an array of ints that are the durations for which to turn on or off the vibrator in milliseconds. The first value indicates the number of milliseconds to wait before turning the vibrator on.
The next value indicates the number of milliseconds for which to keep the vibrator on before turning it off. Subsequent values alternate between durations in milliseconds to turn the vibrator off or to turn the vibrator on.
API >= 26
If the provided pattern is valid (non-null, non-empty), will enable vibration as well. Otherwise, vibration will be disabled. Only modifiable before the channel is submitted.
Here is the code to be added to NB6 class:
B4X:
'Set the vibration pattern to use.
'Pass in an array of ints that are the durations for which to turn on or off the vibrator in milliseconds
'The first value indicates the number of milliseconds to wait before turning the vibrator on.
'The next value indicates the number of milliseconds for which to keep the vibrator on before turning it off... And so on
Public Sub CustomVibrate(vibPatern() As Long) As NB6
 If IsChannel Then
  Channel.RunMethod("setVibrationPattern", Array(vibPatern))
 Else
  If Not(IsOld) Then
   nDefaults = Bit.And(nDefaults, 1 + 4) 'clear bit 1
  End If
  NotificationBuilder.RunMethod("setVibrate", Array(vibPatern))
 End If
 Return Me
End Sub
6. CustomLight
Set the desired color for the indicator LED on the device, as well as the blink duty cycle (specified in milliseconds). Not all devices will honor all (or even any) of these values.
On and Off times can only be used with API < 26. They will be ignored for API >= 26 - pass 0 values
API >= 26
Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. Only modifiable before the channel is submitted to
Here is the code to be added to NB6 class:
B4X:
'Set the desired color for the indicator LED on the device, as well as the blink duty cycle (specified in milliseconds)
'duty cycle ignored on SDK > 25. Use 0, 0 in sub call for onMs and offMs for SDK > 25
Public Sub CustomLight(argb As Int, onMs As Int, offMs As Int) As NB6
 If IsChannel Then
  Channel.RunMethod("setLightColor", Array(argb))
 Else
   If Not(IsOld) Then
    nDefaults = Bit.And(nDefaults, 1 + 2) 'clear bit 2
   End If
   NotificationBuilder.RunMethod("setLights", Array(argb, onMs, offMs))
 End If
 Return Me
End Sub
7. Modified code for CustomSound - explanation why it had to be done, below.
Here is the code to be added to NB6 class:
B4X:
'Sets a custom sound.
'The uri must be created with FileProvider.
Public Sub CustomSound (FileProviderUri As Object) As NB6
 If IsOld Then Return Me
 ctxt.RunMethod("grantUriPermission", Array("com.android.systemui", FileProviderUri, 1))
 If IsBuilder Then
  ' NotificationBuilder.RunMethod("setSound", Array(FileProviderUri, NotificationStatic.GetField("AUDIO_ATTRIBUTES_DEFAULT")))
   If IsChannel Then
    ctxt.RunMethod("grantUriPermission", Array("com.android.systemui", FileProviderUri, 1))
Channel.RunMethod("setSound", Array(FileProviderUri, NotificationStatic.GetField("AUDIO_ATTRIBUTES_DEFAULT")))
   Else
     If Not(IsOld) Then
      nDefaults = Bit.And(nDefaults, 2 + 4) 'clear bit 0
    End If
    NotificationBuilder.RunMethod("setSound", Array(FileProviderUri, NotificationStatic.GetField("AUDIO_ATTRIBUTES_DEFAULT")))
   End If
 End If
 Return Me
End Sub
8. I have noticed that Sub setDefaults(sound, light, vibration) in NB6 doesn't work correctly when used with Notifications with and without Channels
For API >= 26
setDefaults(false, false, false) means - no sound, no lights, no vibration
setDefaults(ture, true, true) means - enable sound (default, or custom if provided), enable light (default color or custom if provided),
enable vibration(default vibration pattern or custom if provided)
For API < 26
setDefaults(false, false, false) means - custom sound, custom lights and custom vibration pattern
setDefaults(true, true, true) means - default sound, default lights color and defualt vibration pattern.

So, true values mean "enable..." (default or custom for API >= 26, and default only for API < 26.
Therefore I had to modify existing sub CustomSound in NB6.
I also had to modify sub Build:

variable nDefaults has to be added to Class_Globals in NB6 class:
B4X:
Sub Class_Globals
Private nDefaults As Int = 7 '1 (default sound) + 2 (default lighs) + 4 (default vib.)
...
...
Here is the code to be changed in Sub Build, in NB6 class:
B4X:
'Build the notification and returns the notification object.
'ContentTitle - Title (CharSequence)
'ContentText - Body text (CharSequence)
'Tag - Tag that can be intercepted in Activity_Resume when the user clicks on the notificaiton.
'Activity - The activity that will be launched when the user clicks on the notification.
Public Sub Build (ContentTitle As Object, ContentText As Object, Tag As String, Activity As Object) As Notification
 If IsOld Then
  OldNotification.SetInfo2(ContentTitle, ContentText, Tag, Activity)
  Return OldNotification
 Else
 If Not(IsChannel) And nDefaults <> 7 Then 'not all true 'added code
  NotificationBuilder.RunMethod("setDefaults", Array(nDefaults))
 End If
 Dim in As Intent = CreateIntent(Activity, False)
  in.Flags = Bit.Or(268435456, 131072) 'FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_REORDER_TO_FRONT
  in.PutExtra("Notification_Tag", Tag)
  Dim PendingIntent As Object = PendingIntentStatic.RunMethod("getActivity", Array(ctxt, Rnd(0, 0x7fffffff), in, 0))
  NotificationBuilder.RunMethodJO("setContentTitle", Array(ContentTitle)).RunMethodJO("setContentText", Array(ContentText))
  NotificationBuilder.RunMethod("setContentIntent", Array(PendingIntent))

  If IsChannel Then
   Dim manager As JavaObject = ctxt.RunMethod("getSystemService", Array("notification"))
   manager.RunMethod("createNotificationChannel", Array(Channel))

  End If
  Return NotificationBuilder.RunMethod("build", Null)
 End If
End Sub
9. Colorized
Set whether this notification should be colorized. When set, the color set with sub Color(int) will be used as the background color of this notification.
This should only be used for high priority ongoing tasks like navigation, an ongoing call, or other similarly high-priority events for the user.
For most styles, the coloring will only be applied if the notification is for a foreground service notification

Note: Although I tried, I couldn't get this method working!!! I added the code just in case it is a Google bug and they will fix it.
Here is the code to be added to NB6 class:
B4X:
'Set whether this notification should be colorized.
'When set, the color set with setColor(int) will be used as the background color of this notification.
Public Sub Colorized(colorize As Boolean) As NB6
 If IsChannel Then
  NotificationBuilder.RunMethod("setColorized", Array(colorize))
 End If
Return Me
End Sub
10. Description - API >= 26 only
Sets the user visible description of this channel.
The recommended maximum length is 300 characters; the value may be truncated if it is too long.
Here is the code to be added to NB6 class:
B4X:
Public Sub Description(text As String) As NB6
 If IsChannel Then
  Channel.RunMethod("setDescription", Array(text))
 End If
 Return Me
End Sub
Here is an example for Inbox Notification using some of other added methods:
B4X:
Sub Inbox_Notification
Dim n As NB6
n.Initialize(chEmail, "Email", "HIGH").SmallIcon(smiley).AutoCancel(False) '.Ongoing(True)
n.Description("notification from Email app")
Dim largeIcon As Bitmap = LoadBitmapResize(File.DirAssets, "logo.png", 256dip, 256dip, True)
n.LargeIcon(largeIcon) 'optional
n.SetDefaults(True, True, True)

Dim v() As Long
v = Array As Long(0, 200) ', 600, 800, 1200, 1600)
n.CustomVibrate(v)

n.CustomLight(Colors.Yellow, 1000, 500) 'values 1000, 500 will be ignored for API >= 26

n.UsesChronometer(True)

n.TimeoutAfter(1000*180) 'clear notification after 180 sec.

Dim msgLine As List
msgLine.Initialize
msgLine.Add("this is line 1")
msgLine.Add("this is line 2")
msgLine.Add("this is line 3")
msgLine.Add("this is line 4")
msgLine.Add("this is line 5")
msgLine.Add("this is line 6")
Dim text As String = "+" & (msgLine.Size-1) & " More"
n.InboxStyle(text, msgLine)
text = "Your have " & msgLine.Size & " New Emails"
n.Build(text, msgLine.Get(0), "tag", Me).Notify(8)
End Sub
Google Description of the new notification with Channels:
Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel. For each channel, you can set the visual and auditory behavior that is applied to all notifications in that channel.
Then, users can change these settings and decide which notification channels from your app should be intrusive or visible at all.
After you create a notification channel, you cannot change the notification behaviors—the user has complete control at that point. Though you can still change a channel's name and description.

Google says that after creation of the channel "the user has complete control at that point". Which is not true. The user has a limited (by Google) control.
We could give user more control with previous Notification API.

To provide the user access to these notification settings, you should add an item in your app's settings UI that opens these system settings.
That's why it is important to give a meaninful "ChannelName" when creating the channel.
I have added a "Description" sub because the Description appears in the App. Notification Settings.
Below is the code to:
1. Open App. Notification Settings - from there the user can choose and open channel setting.
2. Open App. Channel Settings for selected channel - you have to specify channelID that you want to see the seetings for. The user can modify some settings.
3. Delete Channel with specified channelID - unfortunately, this only removes the channel from the App. Notification Settings. When you re-initialise it, it will show up with the settings
that the channel was created with before.

For 2 and 3 you have to be careful, because that ChannelId that you supply to the NB6 Initialize Sub is not the ChannelId that is used when the Channel is created.
There is a line of code in this sub that changes it.
ChannelId = ChannelId & "_" & ImportanceLevel
I think that this line should be removed.
Here is the code:
B4X:
Sub deleteChannel(chID As String)
 Dim jo As JavaObject
 If chID = "" Then
  ToastMessageShow("Invalid channel ID", False)
  Return
 End If
 jo.InitializeContext ' = Me
 jo.RunMethod("DelNotifChannel", Array(chID))
End Sub

Sub OpenAppNotifSet
 Dim jo As JavaObject
 jo.InitializeContext
 jo.RunMethod("openAppSettings", Null)
End Sub

Sub OpenNotifChannelSet(chID As String)
 Dim jo As JavaObject
 If chID = "" Then
  ToastMessageShow("Invalid channel ID", False)
  Return
 End If
 jo.InitializeContext
 jo.RunMethod("openChannelSettings", Array(chID))
End Sub

#if JAVA
import android.content.Context;
import android.app.NotificationManager;
import android.content.Intent;
import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS;
import static android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS;
import static android.provider.Settings.EXTRA_APP_PACKAGE;
import static android.provider.Settings.EXTRA_CHANNEL_ID;

public void DelNotifChannel(String id){
 Context context = BA.applicationContext; 
 NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 notificationManager.deleteNotificationChannel(id);
}

public void openAppSettings(){
 Context context = BA.applicationContext;
 Intent intent = new Intent(ACTION_APP_NOTIFICATION_SETTINGS);
 intent.putExtra(EXTRA_APP_PACKAGE, context.getPackageName());
 startActivity(intent);
}

public void openChannelSettings(String id){
 Context context = BA.applicationContext;
 Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS);
 intent.putExtra(EXTRA_APP_PACKAGE, context.getPackageName());
 intent.putExtra(EXTRA_CHANNEL_ID, id);
 startActivity(intent);
}
#End If
Because the only way to change some channel settings (like notificaion light on/off, color, vibration pattern) it is to unistall the app, or clear app data.
The workaround (if necessary), to modify all channel settings and give the user option to modify them in your app seetings page could be like this:
1. User opens Notification Settings page in your app and makes changes.
2. If the changes are made. Delete the channel.
3. Initialize new channel with new ID and old name and description. A channel ID can be changed for example like this "sms1", "sms2", "sms3" etc.
The only drawback is, that the size of app data increases with every addition of the new channel.

After all this, I can't say the new notification API is any better from the previous one. I thing it is a step back by Google. And it is not the first time.
That's why I will stick to the old Notification API with the NB6.
But to do this I had to modify NB6 Initialze sub, because it is using BuildOS version to determine what notifications to use.
I changed it, to add an option to use targetSDK version specified in the manifest as well
Becuase I don't post my app on the Google PlayStore, I don't care about Google's requirement for targetSDK version
Here is the code just in case someone wants it.
B4X:
#if JAVA
import android.content.Context;
import android.app.NotificationManager;

public int getTargetSDK(){
Context context = BA.applicationContext;
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
return targetSdkVersion;
}
#End If

'Initializes the builder.
'ChannelId - On Android 8+ notifications are grouped by channels. Some of the features belong to the channel
' and not to the specific notification.
' Note that once a notification channel is created, you cannot change its behavior without uninstalling the app.
' NB6 adds the notification level string to the ChannelId so in most cases you can use the same value for all notifications.
'Channel Name - The channel name that appears when pressing on All Categories button (Android 8+).
'ImportanceLevel - MIN, LOW, DEFAULT, HIGH
'MIN - Minimum interrup. Cannot be used with foreground services.
'LOW - Without sound.
'DEFAULT - With sound.
'HIGH - Might appear as a headup notification.
'useBuildVersionSDK - TRUE use OS build SDK version. If FALSE use TargetSDK Version from manifest
Public Sub Initialize (ChannelId As String, ChannelName As Object, ImportanceLevel As String, useBuildVersionSDK As Boolean) As NB6
ctxt.InitializeContext
PendingIntentStatic.InitializeStatic("android.app.PendingIntent")
NotificationStatic.InitializeStatic("android.app.Notification")
common.InitializeStatic("anywheresoftware.b4a.keywords.Common")
Dim jo As JavaObject
If useBuildVersionSDK Then
SdkLevel = jo.InitializeStatic("android.os.Build$VERSION").GetField("SDK_INT")
Else 'targetSDK
jo = Me
SdkLevel = jo.RunMethod("getTargetSDK", Null)
End If
If SdkLevel < 23 Then
Log("support sOld")
SupportLevel = S_OLD
Else if SdkLevel >= 26 Then
Log("support sChannel " & ChannelId & " " & ChannelName)
SupportLevel = S_CHANNEL
Else
Log("support sBuilder")
SupportLevel = S_BUILDER
End If
If IsOld Then
OldNotification.Initialize
OldNotification.Icon = "icon"
Else if IsChannel Then
NotificationBuilder.InitializeNewInstance("android.app.Notification$Builder", Array(ctxt, ChannelId))
Dim im As Map = CreateMap("MIN": 1, "LOW": 2, "DEFAULT": 3, "HIGH": 4)
Dim i As Int = im.Get(ImportanceLevel)
Channel.InitializeNewInstance("android.app.NotificationChannel", Array(ChannelId, ChannelName, i))
Else
NotificationBuilder.InitializeNewInstance("android.app.Notification$Builder", Array(ctxt))
Dim pm As Map = CreateMap("MIN": -2, "LOW": -1, "DEFAULT": 0, "HIGH": 1)
Dim p As Int = pm.Get(ImportanceLevel)
NotificationBuilder.RunMethod("setPriority", Array(p))
End If
If ImportanceLevel = "DEFAULT" Or ImportanceLevel = "HIGH" Then
SetDefaults(True, True, True)
Else
SetDefaults(False, True, True)
End If
nDefaults = 1+2+4 'all true sound, vibration, light
Return Me
End Sub
There are some other methods available in new Notification API which could be implemented as well.
 

wes58

Active Member
Licensed User
Users can modify some settings for notification channels, including behaviors such as importance, vibration and alert sound. So If you'd like to know the settings a user has applied to your notification channels, you can get the settings for a specific channel (you have to provide a channelID) or get settings for all notification channels.
If you detect a channel setting that you believe inhibits the intended behavior for your app, you can suggest the user change it and provide an action to open the Channel settings (functions in post #1)
You can also show on the screen (icons/text) what the settings are when you create a channel and after the user changes any of the settings.

Here are 2 functions that may be useful if you want to check if the user has changed the Notification Channel settings.

B4X:
Sub GetNotifChannel(channelID As String)   
    Dim Channel As JavaObject
    Dim m As Map
    m.Initialize
    Channel.InitializeContext
    m = Channel.RunMethod("mGetNotifChannel", Array(channelID))
    If Not(m.IsInitialized) Then        'return from call = Null
        ToastMessageShow("Invalid Channel Id "  & channelID, True)
        Return
    End If
    Dim Id As String  = m.Get("Id")
    Dim importance As Int = m.Get("Importance")
    Dim lightcolor As Int = m.Get("Lightcolor")
    Dim shouldVibrate As Boolean = m.Get("Vibrate")
    Dim shouldShowLight As Boolean = m.Get("ShowLight")
    Dim sound As Uri = m.Get("Sound")
    Dim canShowBadge As Boolean = m.Get("ShowBadge")
    Dim canBypassDnd As Boolean = m.Get("BypassDnd")

    Log("Channel Id = " & Id)
    Log("Importance = " & importance)
    Log("Light color = " & lightcolor)
    Log("Vibrate = " & shouldVibrate)
    Log("Show Light " & shouldShowLight)
    Log("Show Badge = " & canShowBadge)
    Log("Bypass the 'Do Not Disturb' = " & canBypassDnd)
    Log("Sound URI = " & sound)
End Sub

Public Sub GetAllNotifChannells
    Dim l As List
    Dim i As Int
    Dim Channel As JavaObject
    Channel.InitializeContext
    l.Initialize
    l = Channel.RunMethod("mGetNotifChannels", Null)
    If Not(l.IsInitialized) Then        'list wac set to Null because couldn't get notidication channels
        ToastMessageShow("Error getting Notification Channels", True)
        Return
    End If
    For i = 0 To l.Size-1
        Dim m As Map
        m.Initialize
        m = l.Get(i)
        Dim Id As String  = m.Get("Id")
        Dim importance As Int = m.Get("Importance")
        Dim lightcolor As Int = m.Get("Lightcolor")
        Dim shouldVibrate As Boolean = m.Get("Vibrate")
        Dim shouldShowLight As Boolean = m.Get("ShowLight")
        Dim sound As Uri = m.Get("Sound")
        Dim canShowBadge As Boolean = m.Get("ShowBadge")
        Dim canBypassDnd As Boolean = m.Get("BypassDnd")

        Log("item " & i )
        Log("Channel Id = " & Id)
        Log("Importance = " & importance)
        Log("Light color = " & lightcolor)
        Log("Vibrate = " & shouldVibrate)
        Log("Show Light " & shouldShowLight)
        Log("Show Badge = " & canShowBadge)
        Log("Bypass the 'Do Not Disturb' = " & canBypassDnd)
        Log("Sound URI = " & sound)
    Next
End Sub

#if JAVA
import java.util.ArrayList;
import android.content.Context;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.os.Build;
import java.util.List;
import android.net.Uri;
import anywheresoftware.b4a.objects.collections.Map;

public  List<Object> mGetNotifChannels() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Context context = BA.applicationContext;  
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null) {
             return null;
        }
        List<NotificationChannel> l = notificationManager.getNotificationChannels();
        List<Object> lMap = new ArrayList();
        int i;
        for(i = 0; i < l.size()-1; ++i){
            NotificationChannel channel = l.get(i);
            Object o = getMap(channel);
               lMap.add(o);
        }
        return lMap;  
    }
    return null;
}  

public Object mGetNotifChannel(String channelId){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Context context = BA.applicationContext;  
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null) {
             return null;
        }
        NotificationChannel channel =  notificationManager.getNotificationChannel(channelId);
        if(channel == null){
            return null;
        }
        return getMap(channel);
    }
    return null;
}

public Object getMap(NotificationChannel channel){
    String Id = channel.getId();
    int importance = channel.getImportance();
    int lightcolor = channel.getLightColor();
    Boolean shVibrate = channel.shouldVibrate();
    Boolean shShowLight = channel.shouldShowLights();
    Boolean canShowBadge = channel.canShowBadge();
    Boolean canBypassDnd = channel.canBypassDnd();
    Uri sound = channel.getSound();
    anywheresoftware.b4a.objects.collections.Map map = new anywheresoftware.b4a.objects.collections.Map();
    map.Initialize();
    map.Put("Id", Id);
    map.Put("Sound", sound);
    map.Put("Importance", importance);
    map.Put("Lightcolor", lightcolor);
    map.Put("Vibrate", shVibrate);
    map.Put("ShowLight", shShowLight);
    map.Put("ShowBadge", canShowBadge);
    map.Put("BypassDnd", canBypassDnd);
    return map.getObject();
}
#End If
 

wes58

Active Member
Licensed User
Maybe it is not directly related to NB6, but that's when I found the need for this function.
I had the following situation: I selected a notification sound via ringtone picker and stored the name and the uri of the selected sound.
After firmware update of the phone, or using different phone, I found out that the uri I stored doesn't correspond to the name of the notification sound.
That's why, I needed a function where I can find the uri of the notification sound if I know the name, and without using again the ringtone picker.

Here is a example of the sub and java function:
B4X:
Sub getURI(title As String)
    Dim jo As JavaObject

    jo.InitializeContext 
    uri = jo.RunMethod("getSoundUri", Array(title))
    If Not(uri.IsInitialized) Then                    'not found
        ToastMessageShow("Notification sound " & title & " not found", True)
    Else
        'do something with the uri
    End If
End Sub

#if JAVA
import android.content.Context;
import android.database.Cursor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.provider.MediaStore;

//Note that the list of ringtones/notification sounds available will differ depending on whether
//you have in the Manifest.permission.READ_EXTERNAL_STORAGE permission.

public Uri getSoundUri(String ringtoneTitle) {
    Uri foundUri;
    Context context = BA.applicationContext;
    RingtoneManager rm = new RingtoneManager(context);
    rm.setType(RingtoneManager.TYPE_NOTIFICATION);        //notification sounds
//    rm.setType(RingtoneManager.TYPE_RINGTONE);            //ringtone sounds
    Cursor cursor = rm.getCursor();
    cursor.moveToFirst();

    while (cursor.moveToNext()) {
          if(ringtoneTitle.compareToIgnoreCase(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)) == 0) {
            int ringtoneID = cursor.getInt(RingtoneManager.ID_COLUMN_INDEX);
            foundUri = rm.getRingtoneUri(cursor.getPosition());   
//            String name = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
//            BA.Log(name);
//            BA.Log(foundUri.toString());
            return foundUri;
        }   
    }
    return null;
}
#End If
Note that the list of ringtone/notification sounds available will differ depending on whether you have the Manifest.permission.READ_EXTERNAL_STORAGE permission.
 

wes58

Active Member
Licensed User
Create a Group of Notifications
Starting from Android 7.0 you can display related notifications in a group. You can read more about it on https://developer.android.com/training/notify-user/group.html and https://blog.danlew.net/2017/02/07/correctly-handling-bundled-android-notifications/
Android Grouped Notifications are handy when you have too many notifications stacked vertically. Groupped Notifications collapse them all into a single notification with a group summary set on the group notification. This saves us from scrolling through all the notifications. Also, if you receive many single notifications at once, instead of getting multiple noisy notification sounds, group notifications would give a single notification sound.

To build a grouped notification you have to create a Summary Notification and a number of Single Notifications. Whenever you send a Single notification you have to send a Summary notification as well.
Every notification will have the same group key.
When a new notification is added, it gets merged in the group with the matching group key.

Here is an example code to do it:
Run sub Send_Group_Notification a couple of times to create a number of notifications. They will be grouped in one notification which, when expanded, shows all notifications.

The sub Cancel_Notification cancels one notification at a time.
The problem (Google's design) with canceling notifications by code is, that when you remove the last notification, the Summary notification stay on. That's why you need to keep track of the number of notifications issued and removed. When there is only one left, and it is cleared you have to clear the Summary notification.
This issue, is only when you clear notification by code.
When you swipe the notification to clear it, all of them (including Summary) will be cleared.
B4X:
Sub Process_Globals

  
    Public const grpNotifyID As Int = 101
    Public const sumNotifyID As Int = 100
    Public const groupID As String = "test_group"
    Public const groupName As String = "Test Group"
    Public const grpChnId As String = "chnTest"
    Public const grpChnName As String = "Test Channel"
    Public const sumChnId As String = "chnSummary"
    Public const sumChnName As String = "Summary Channel"
    Public curNotifyID As Int = grpNotifyID
  
    Public remNotifyID As Int = 0
  
End Sub

'Each time you send a notification you have to send a Group Summary notification as well.
Sub Send_Group_Notification
    Dim n As NB6
    n.Initialize(grpChnId, grpChnName, "DEFAULT").AutoCancel(True).SmallIcon(smiley)
    n.SetDefaults(True, True, True)
    'create notification channel group
    n.NotificationChannelGroup(groupID, groupName)
  
    n.GroupSet(groupID)
    n.GroupSummary(False)
    Dim contentText As String = "Message with ID " & curNotifyID
    Dim contentTitle As String = "New Group Message"
    n.Build(contentTitle, contentText, "Tag", Me).Notify(curNotifyID)
'you have to send notification with different ID each time
    curNotifyID = curNotifyID + 1
  
    Dim nSum As NB6
    nSum.Initialize(sumChnId, sumChnName, "LOW").AutoCancel(True).SmallIcon(smiley)
    'we don't need any notification sound/vibration
    nSum.SetDefaults(False, False, False)
    'create notification channel group
    nSum.NotificationChannelGroup(groupID, groupName)
  
    nSum.GroupSet(groupID)
    nSum.GroupSummary(True)
  
    Dim contentText As String = "Summary Notification"
    Dim contentTitle As String = "Group " & grpChnName
    nSum.Build(contentTitle, contentText, "Tag", Me).Notify(sumNotifyID)
End Sub

Sub Cancel_Notification
    If remNotifyID = 0 Then
        remNotifyID = curNotifyID
    End If
    If remNotifyID = grpNotifyID Then
        Return
    End If
  
    'clear notification
    Dim ctx As JavaObject
    ctx.InitializeContext
    ToastMessageShow("cancelled notification with ID " & remNotifyID, False)
    Dim manager As JavaObject = ctx.RunMethod("getSystemService", Array("notification"))
    manager.RunMethod("cancel", Array(remNotifyID))
  
    remNotifyID = remNotifyID - 1
    'you need to clear the Summary Notification when you clear the last one.
    If remNotifyID = grpNotifyID Then
        ToastMessageShow("cancelled group summary notification", False)
        manager.RunMethod("cancel", Array(sumNotifyID))
        curNotifyID = grpNotifyID
        remNotifyID = 0
    End If
End Sub
And here are the changes to the NB6 class module:
Add to Class_Globals:
B4X:
Sub Class_Globals

    Private NotifChannelGroup As JavaObject    
    Private groupID As String
End Sub
Add in NB6 class, to Sub Build, the marked lines:
B4X:
Public Sub Build (ContentTitle As Object, ContentText As Object, Tag As String, Activity As Object) As Notification

...
...
        If IsChannel Then
            Dim manager As JavaObject = ctxt.RunMethod("getSystemService", Array("notification"))

'*** add lines below
            'create notification Channel Group
            If NotifChannelGroup.IsInitialized Then
                 manager.RunMethod("createNotificationChannelGroup", Array(NotifChannelGroup))
                 Channel.RunMethod("setGroup", Array(groupID))
            End If
'*** end changes

           manager.RunMethod("createNotificationChannel", Array(Channel))      
        End If
        Return NotificationBuilder.RunMethod("build", Null)
    End If
End Sub
Add the following subs in NB6 class
B4X:
'Create Group for Notification Channel
'grpID - id of the group created with sub NotificationChannelGroup
'grpName - Name of the Group displayed on the App. Notification Setting screen
Public Sub NotificationChannelGroup(grpID As String, grpName As String)
    If IsChannel Then
        groupID = grpID
        NotifChannelGroup.InitializeNewInstance("android.app.NotificationChannelGroup", Array(grpID, grpName))
    End If
End Sub

'Set the group for the Notification
'groupID - id of the group created the same as created with sub NotificationChannelGroup
Public Sub GroupSet(grpID As String) As NB6
    If IsBuilder Then
        NotificationBuilder.RunMethod("setGroup", Array(grpID))
    End If
    Return Me
End Sub

'Set group summary
True - only for Summary Notifications
'False - false for other notifications in a group
Public Sub GroupSummary(isGroupSummary As Boolean) As NB6
    If IsBuilder Then
        NotificationBuilder.RunMethod("setGroupSummary", Array(isGroupSummary))
    End If
    Return Me
End Sub
 

Mike1970

Well-Known Member
Licensed User
Dim nSum As NB6
nSum.Initialize(sumChnId, sumChnName,
"LOW").AutoCancel(True).SmallIcon(smiley)
'we don't need any notification sound/vibration
nSum.SetDefaults(False, False, False)
'create notification channel group
nSum.NotificationChannelGroup(groupID, groupName)

nSum.GroupSet(groupID)
nSum.GroupSummary(
True)

Dim contentText As String = "Summary Notification"
Dim contentTitle As String = "Group " & grpChnName
nSum.Build(contentTitle, contentText,
"Tag", Me).Notify(sumNotifyID)

Awesome work.
But i didn’t understand what is supposed to do this part.
I tried to delete it to figure out what it does but there are no changes.
 

wes58

Active Member
Licensed User
You can read more about it on https://developer.android.com/training/notify-user/group.html
Here are two pictures attached showing what it does:
It creates summary notifications (bundled notification)- see pic01.png - small number '2" indicates that there are two notifications. When you click on the text at the top of the notification, it will expand showing details of the two notifications - see pic02.png.
 

Attachments

Mike1970

Well-Known Member
Licensed User
When you click on the text at the top of the notification, it will expand showing details of the two notifications
Yeah, but those notification are created by using this part
Dim n As NB6
n.Initialize(grpChnId, grpChnName, "DEFAULT").AutoCancel(True).SmallIcon(smiley)
n.SetDefaults(True, True, True)
'create notification channel group
n.NotificationChannelGroup(groupID, groupName)

n.GroupSet(groupID)
n.GroupSummary(False)
Dim contentText As String = "Message with ID " & curNotifyID
Dim contentTitle As String = "New Group Message"
n.Build(contentTitle, contentText, "Tag", Me).Notify(curNotifyID)
'you have to send notification with different ID each time
curNotifyID = curNotifyID + 1
My doubt is with the Summary (the only thing i noticed is this: pic1)
But in notification center i have only this (pic2) nothing regarding summary
 

Attachments

wes58

Active Member
Licensed User
You have explanation in the comments in the code "create notification channel group
The attached picture shows what I have in my application. When you click on it it will show the channel settings for group notifications (on the right of the photo).

For you application, you can see it when you go to Settings-App. And find you application, and in "App Info" click on "Notifications". You should see the channel for group notification with the name that you have defined.
 

Attachments

Mike1970

Well-Known Member
Licensed User
You have explanation in the comments in the code "create notification channel group
The attached picture shows what I have in my application. When you click on it it will show the channel settings for group notifications (on the right of the photo).

For you application, you can see it when you go to Settings-App. And find you application, and in "App Info" click on "Notifications". You should see the channel for group notification with the name that you have defined.
Ok.. it's slightly different for me. Thanks anyway
 
Top