Skip to content

Commit a31ba24

Browse files
authored
Merge pull request #39 from OutSystems/feat/RMET-4280/custom-sound
RMET-4280 :: Local Notifications Plugin ::: Custom Sounds MABS 11 and 12
2 parents 633afe9 + cef14f1 commit a31ba24

File tree

8 files changed

+146
-28
lines changed

8 files changed

+146
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ ChangeLog
33

44
Please also read the [Upgrade Guide](https://github.com/katzer/cordova-plugin-local-notifications/wiki/Upgrade-Guide) for more information.
55

6+
#### Version 0.9.16 (05.09.2025)
7+
### 05-09-2025
8+
- Fixes an issue where custom notification sounds were not playing. [RMET-4280](https://outsystemsrd.atlassian.net/browse/RMET-4280)
9+
610
#### Version 0.9.15 (02.07.2025)
711
### 26-06-2025
812
- iOS: Add hook to add set `handleApplicationNotifications` (https://outsystemsrd.atlassian.net/browse/RMET-3658).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cordova-plugin-local-notification",
3-
"version": "0.9.15",
3+
"version": "0.9.16",
44
"description": "Schedules and queries for local notifications",
55
"cordova": {
66
"id": "cordova-plugin-local-notification",

plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
2525
xmlns:android="http://schemas.android.com/apk/res/android"
2626
id="cordova-plugin-local-notification"
27-
version="0.9.15">
27+
version="0.9.16">
2828

2929
<name>LocalNotification</name>
3030

src/android/notification/Builder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public Notification build() {
118118
return new Notification(context, options);
119119
}
120120

121-
Uri sound = options.getSound();
121+
Uri sound = options.getSoundUri();
122122
Bundle extras = new Bundle();
123123

124124
extras.putInt(Notification.EXTRA_ID, options.getId());

src/android/notification/Manager.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@
2323

2424
package de.appplant.cordova.plugin.notification;
2525

26-
import android.annotation.SuppressLint;
2726
import android.app.AlarmManager;
2827
import android.app.NotificationChannel;
2928
import android.app.NotificationManager;
3029
import android.content.Context;
3130
import android.content.SharedPreferences;
31+
import android.media.AudioAttributes;
32+
import android.media.RingtoneManager;
33+
import android.net.Uri;
3234
import android.os.Build;
3335
import android.service.notification.StatusBarNotification;
36+
3437
import androidx.core.app.NotificationManagerCompat;
3538

3639
import org.json.JSONException;
@@ -41,11 +44,10 @@
4144
import java.util.Set;
4245

4346
import de.appplant.cordova.plugin.badge.BadgeImpl;
47+
import de.appplant.cordova.plugin.notification.util.AssetUtil;
4448

4549
import static android.os.Build.VERSION.SDK_INT;
4650
import static android.os.Build.VERSION_CODES.M;
47-
import static android.os.Build.VERSION_CODES.O;
48-
import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
4951
import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY_ID;
5052
import static de.appplant.cordova.plugin.notification.Notification.Type.SCHEDULED;
5153
import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED;
@@ -73,7 +75,6 @@ public final class Manager {
7375
*/
7476
private Manager(Context context) {
7577
this.context = context;
76-
createDefaultChannel();
7778
}
7879

7980
/**
@@ -109,32 +110,44 @@ public boolean canScheduleExactAlarms() {
109110
*/
110111
public Notification schedule (Request request, Class<?> receiver) {
111112
Options options = request.getOptions();
113+
createChannel(options);
112114
Notification toast = new Notification(context, options);
113115

114116
toast.schedule(request, receiver);
115117

116118
return toast;
117119
}
118120

119-
/**
120-
* TODO: temporary
121-
*/
122-
@SuppressLint("WrongConstant")
123-
private void createDefaultChannel() {
124-
NotificationManager mgr = getNotMgr();
121+
private void createChannel(Options options) {
122+
String channelId = options.getChannel();
123+
NotificationManager notificationManager = getNotMgr();
124+
NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
125+
if (channel != null) return;
125126

126-
if (SDK_INT < O)
127-
return;
127+
channel = new NotificationChannel(channelId, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
128128

129-
NotificationChannel channel = mgr.getNotificationChannel(CHANNEL_ID);
129+
Uri soundUri = getSoundUri(options.getSound());
130+
channel.setSound(
131+
soundUri,
132+
new AudioAttributes.Builder()
133+
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
134+
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
135+
.build()
136+
);
130137

131-
if (channel != null)
132-
return;
138+
notificationManager.createNotificationChannel(channel);
139+
}
133140

134-
channel = new NotificationChannel(
135-
CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT);
141+
private Uri getSoundUri(String soundPath) {
142+
if (soundPath == null || soundPath.isEmpty()) {
143+
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
144+
}
136145

137-
mgr.createNotificationChannel(channel);
146+
Uri soundUri = AssetUtil.getInstance(context).parse(soundPath);
147+
if (soundUri == null || soundUri.equals(Uri.EMPTY)) {
148+
soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
149+
}
150+
return soundUri;
138151
}
139152

140153
/**

src/android/notification/Options.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,14 @@ long getTimeout() {
215215
* The channel id of that notification.
216216
*/
217217
String getChannel() {
218-
return options.optString("channel", Manager.CHANNEL_ID);
218+
String channelId = options.optString("channel", null);
219+
if (channelId == null || channelId.isEmpty()) {
220+
String soundPath = getSound();
221+
String soundSuffix = soundPath.isEmpty() ? "" : "." + soundPath.substring(soundPath.lastIndexOf('/') + 1);
222+
channelId = Manager.CHANNEL_ID + "." + soundSuffix;
223+
}
224+
225+
return channelId;
219226
}
220227

221228
/**
@@ -347,8 +354,15 @@ public int getColor() {
347354
/**
348355
* Sound file path for the local notification.
349356
*/
350-
Uri getSound() {
351-
return assets.parse(options.optString("sound", null));
357+
String getSound() {
358+
return options.optString("sound", null);
359+
}
360+
361+
/**
362+
* Sound file URI for the local notification.
363+
*/
364+
Uri getSoundUri() {
365+
return assets.parse(getSound());
352366
}
353367

354368
/**

src/android/notification/util/AssetUtil.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import android.graphics.Bitmap;
2929
import android.graphics.BitmapFactory;
3030
import android.net.Uri;
31+
import android.os.Environment;
3132
import android.os.StrictMode;
3233
import android.util.Log;
3334

@@ -94,7 +95,61 @@ public Uri parse (String path) {
9495
return Uri.parse(path);
9596
}
9697

97-
return Uri.EMPTY;
98+
return resolveAssetFromFilePath(path);
99+
}
100+
101+
public Uri resolveAssetFromFilePath(String path) {
102+
String[] searchFolders = {"www", "public"};
103+
AssetManager assetManager = context.getAssets();
104+
105+
String base = path.contains(".") ? path.substring(0, path.lastIndexOf(".")) : path;
106+
String ext = path.contains(".") ? path.substring(path.lastIndexOf(".") + 1) : "";
107+
108+
String resolvedPath = "";
109+
for (String folder : searchFolders) {
110+
try {
111+
String[] files = assetManager.list(folder);
112+
if (files == null) continue;
113+
114+
for (String f : files) {
115+
if (f.equalsIgnoreCase(path)) {
116+
resolvedPath = folder + "/" + f;
117+
break;
118+
}
119+
}
120+
121+
String regex = "^" + base + "__.+\\." + ext + "$";
122+
for (String f : files) {
123+
if (f.matches("(?i)" + regex)) {
124+
resolvedPath = folder + "/" + f;
125+
break;
126+
}
127+
}
128+
129+
} catch (IOException e) {
130+
e.printStackTrace();
131+
}
132+
}
133+
134+
if (resolvedPath.isEmpty()) {
135+
return Uri.EMPTY;
136+
}
137+
138+
String fileName = resolvedPath.substring(resolvedPath.lastIndexOf('/') + 1);
139+
File file = new File(context.getExternalFilesDir(null), fileName);
140+
141+
try {
142+
if (!file.exists()) {
143+
InputStream in = assetManager.open(resolvedPath);
144+
FileOutputStream out = new FileOutputStream(file);
145+
copyFile(in, out);
146+
}
147+
} catch (Exception e) {
148+
e.printStackTrace();
149+
return Uri.EMPTY;
150+
}
151+
152+
return getUriFromFile(file);
98153
}
99154

100155
/**

src/ios/APPNotificationOptions.m

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,45 @@ - (UNNotificationSound*) sound
173173
}
174174

175175
if (!path.length)
176-
return NULL;
176+
return [UNNotificationSound defaultSound];
177177

178178
if ([path hasPrefix:@"file:/"]) {
179179
file = [self soundNameForAsset:path];
180-
} else
181-
if ([path hasPrefix:@"res:"]) {
180+
} else if ([path hasPrefix:@"res:"]) {
182181
file = [self soundNameForResource:path];
182+
} else {
183+
NSBundle *mainBundle = [NSBundle mainBundle];
184+
NSString *resPath = [mainBundle resourcePath];
185+
NSError *error = nil;
186+
187+
NSArray<NSString *> *searchFolders = @[@"www", @"public"];
188+
189+
for (NSString *folder in searchFolders) {
190+
NSString *folderPath = [resPath stringByAppendingPathComponent:folder];
191+
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:&error];
192+
193+
if (!files) {
194+
continue;
195+
}
196+
197+
NSString *base = [path stringByDeletingPathExtension];
198+
NSString *ext = [path pathExtension];
199+
NSString *exactFile = [NSString stringWithFormat:@"%@.%@", base, ext];
200+
201+
if ([files containsObject:exactFile]) {
202+
file = [NSString stringWithFormat:@"%@/%@", folder, exactFile];
203+
break;
204+
}
205+
206+
NSString *pattern = [NSString stringWithFormat:@"^%@__.+\\.%@$", base, ext];
207+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES[c] %@", pattern];
208+
NSArray *matches = [files filteredArrayUsingPredicate:predicate];
209+
210+
if (matches.count > 0) {
211+
file = [NSString stringWithFormat:@"%@/%@", folder, matches[0]];
212+
break;
213+
}
214+
}
183215
}
184216

185217
return [UNNotificationSound soundNamed:file];

0 commit comments

Comments
 (0)