Revision: 2592
http://sourceforge.net/p/swingme/code/2592
Author: yuranet
Date: 2021-11-18 16:28:56 +0000 (Thu, 18 Nov 2021)
Log Message:
-----------
can show local Notifications
Modified Paths:
--------------
iOSME/src/javax/microedition/midlet/MIDlet.java
iOSME/src/net/yura/ios/SwingMEiOSApplication.java
iOSME/src/net/yura/ios/iOSImageEncoder.java
Added Paths:
-----------
iOSME/src/net/yura/ios/iOSNotifications.java
iOSME/src/net/yura/ios/iOSUtil.java
Modified: iOSME/src/javax/microedition/midlet/MIDlet.java
===================================================================
--- iOSME/src/javax/microedition/midlet/MIDlet.java 2021-11-12 15:41:42 UTC (rev 2591)
+++ iOSME/src/javax/microedition/midlet/MIDlet.java 2021-11-18 16:28:56 UTC (rev 2592)
@@ -1,37 +1,43 @@
package javax.microedition.midlet;
+import net.yura.ios.iOSNotifications;
import net.yura.ios.WebViewControllear;
-import java.util.Properties;
+import net.yura.mobile.util.Url;
+import java.util.Map;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.lcdui.Display;
import apple.c.Globals;
-import apple.foundation.NSOperationQueue;
+import apple.foundation.NSBundle;
import apple.foundation.NSThread;
import apple.foundation.NSURL;
-import apple.foundation.NSURLComponents;
import apple.uikit.UIApplication;
import apple.uikit.UINavigationController;
import apple.uikit.UIPasteboard;
+import apple.uikit.enums.UIApplicationState;
public abstract class MIDlet {
+ // standard protocols
public static final String PROTOCOL_HTTP = "http://";
public static final String PROTOCOL_HTTPS = "https://";
public static final String PROTOCOL_SMS = "sms:";
public static final String PROTOCOL_PHONE = "tel:";
public static final String PROTOCOL_EMAIL = "email:";
+
+ // swingme features
public static final String PROTOCOL_NOTIFY = "notify:";
+ public static final String PROTOCOL_CLIPBOARD = "clipboard:";
+ public static final String PROTOCOL_WAKELOCK = "wakelock:";
+
+ // native ios
public static final String PROTOCOL_NATIVE = "native:";
- public static final String PROTOCOL_NATIVE_NO_RESULT = "nativeNoResult:";
+ public static final String PROTOCOL_NATIVE_FOR_RESULT = "nativeForResult:";
public static MIDlet DEFAULT_MIDLET;
- public static Properties DEFAULT_APPLICATION_PROPERTIES;
private static String platformLastUrl;
private static long platformLastTime;
- private Properties applicationProperties = DEFAULT_APPLICATION_PROPERTIES;
-
protected MIDlet() {
DEFAULT_MIDLET = this;
}
@@ -47,10 +53,19 @@
}
+ /**
+ * kind of almost the same as pauseApp but is NOT called when the user quits the app themselves
+ * TODO maybe could be removed one day and functionality merged with pauseApp
+ * @see #pauseApp()
+ */
public void saveState() {
}
+ // not part of J2ME, there is no J2ME way of doing this
+ public void pushNotificationsToken(String token) { }
+ public void openNotification(Map params) { }
+
public final void notifyDestroyed() {
Globals.exit(0);
@@ -80,6 +95,9 @@
this.destroyApp(unconditional);
}
+ /**
+ * @return true if the MIDlet suite MUST first exit before the content can be fetched. (always FALSE)
+ */
public boolean platformRequest(String url) throws ConnectionNotFoundException {
try {
@@ -87,31 +105,32 @@
//NSURLComponents nsurlComponents = NSURLComponents.componentsWithString(url);
boolean isProtoNative = url.startsWith(PROTOCOL_NATIVE);
- boolean isProtoNativeNoRes = url.startsWith(PROTOCOL_NATIVE_NO_RESULT);
+ boolean isProtoNativeForRes = url.startsWith(PROTOCOL_NATIVE_FOR_RESULT);
- if (isProtoNative || isProtoNativeNoRes) {
+ if (isProtoNative || isProtoNativeForRes) {
// TODO launch native stuff
throw new UnsupportedOperationException();
}
+ else if (url.startsWith(PROTOCOL_NOTIFY + "//requestAuthorization")) {
+ iOSNotifications.requestNotificationAuthorization();
+ }
else if (url.startsWith(PROTOCOL_NOTIFY)) {
- // there is a bug on android older versions so we use our Url to decode.
-// Map<String, String> options = Url.toHashtable(content.getQuery());
-// String title = options.remove("title");
-// String num = options.remove("num");
-// String message = options.remove("message");
-// String icon = options.remove("icon");
-// String onlyBackground = options.remove("onlyBackground");
- // TODO showNotification(title, num, message, icon, onlyBackground, options);
- throw new UnsupportedOperationException();
+ Map<String, String> options = Url.toHashtable(nsUrl.query());
+ String title = options.remove("title");
+ String num = options.remove("num");
+ String message = options.remove("message");
+ String icon = options.remove("icon"); // iOS does not allow other icons, so we ignore this param
+ String onlyBackgroundString = options.remove("onlyBackground");
+
+ boolean onlyBackground = onlyBackgroundString == null ? false : Boolean.parseBoolean(onlyBackgroundString);
+ int number = num == null ? 0 : Integer.parseInt(num);
+
+ if (!onlyBackground || UIApplication.sharedApplication().applicationState() != UIApplicationState.Active) {
+ iOSNotifications.showNotification(title, message, number, options);
+ }
}
- else if (url.startsWith("toast://show")) {
- // there is a bug on android older versions so we use our Url to decode.
- //Url myurl = new Url( url );
- // TODO showToast(myurl.getQueryParameter("message"),"SHORT".equals(myurl.getQueryParameter("duration"))?Toast.LENGTH_SHORT:Toast.LENGTH_LONG);
- throw new UnsupportedOperationException();
- }
- else if (url.equals("clipboard://get")) {
+ else if (url.equals(PROTOCOL_CLIPBOARD + "//get")) {
UIPasteboard clipboard = UIPasteboard.generalPasteboard();
String text = clipboard.string();
@@ -122,7 +141,7 @@
System.setProperty("clipboard.text", text); // so far we only support Strings
}
}
- else if (url.startsWith("clipboard://put/")) {
+ else if (url.startsWith(PROTOCOL_CLIPBOARD + "//put/")) {
UIPasteboard clipboard = UIPasteboard.generalPasteboard();
String text = url.substring("clipboard://put/".length());
@@ -130,7 +149,7 @@
clipboard.setString(text);
}
}
- else if (url.equals("wakelock://true")) {
+ else if (url.equals(PROTOCOL_WAKELOCK + "//true")) {
callOnMainThread(new Runnable() {
@Override
public void run() {
@@ -138,7 +157,7 @@
}
});
}
- else if (url.equals("wakelock://false")) {
+ else if (url.equals(PROTOCOL_WAKELOCK + "//false")) {
callOnMainThread(new Runnable() {
@Override
public void run() {
@@ -146,11 +165,6 @@
}
});
}
- else if (url.startsWith("geo:")) {
- throw new UnsupportedOperationException();
- // eg (with the brackets and everything else past the = URL encoded): geo://lat,long?q=lat,long(description)
- // TODO open map
- }
else if (url.startsWith(WebViewControllear.PROTOCOL_ASSET)) {
WebViewControllear webViewControllear = WebViewControllear.alloc().init();
@@ -192,9 +206,13 @@
}
public String getAppProperty(String key) {
- return this.applicationProperties.getProperty(key);
+ NSBundle mainBundle = NSBundle.mainBundle();
+ return (String)mainBundle.objectForInfoDictionaryKey(key);
}
+ /**
+ * @return 0 if the permission is denied; 1 if the permission is allowed; -1 if the status is unknown
+ */
public int checkPermission(String string) {
return 0;
}
Modified: iOSME/src/net/yura/ios/SwingMEiOSApplication.java
===================================================================
--- iOSME/src/net/yura/ios/SwingMEiOSApplication.java 2021-11-12 15:41:42 UTC (rev 2591)
+++ iOSME/src/net/yura/ios/SwingMEiOSApplication.java 2021-11-18 16:28:56 UTC (rev 2592)
@@ -13,7 +13,9 @@
import apple.foundation.NSArray;
import apple.foundation.NSBundle;
import apple.foundation.NSCoder;
+import apple.foundation.NSData;
import apple.foundation.NSDictionary;
+import apple.foundation.NSError;
import apple.foundation.NSProcessInfo;
import apple.foundation.c.Foundation;
import apple.foundation.enums.NSSearchPathDirectory;
@@ -87,6 +89,7 @@
// setup and launch swingme app
setupSystemProperties();
+ iOSNotifications.init();
NativeTextEntry.init();
NSBundle mainBundle = NSBundle.mainBundle();
@@ -198,4 +201,16 @@
public boolean applicationShouldRestoreSecureApplicationState(UIApplication application, NSCoder coder) {
return true;
}
+
+ @Override
+ public void applicationDidRegisterForRemoteNotificationsWithDeviceToken(UIApplication application, NSData deviceToken) {
+ byte[] bytes = iOSUtil.toBytes(deviceToken);
+ String tokenString = IntegralToString.bytesToHexString(bytes, true);
+ MIDlet.DEFAULT_MIDLET.pushNotificationsToken(tokenString);
+ }
+
+ @Override
+ public void applicationDidFailToRegisterForRemoteNotificationsWithError(UIApplication application, NSError error) {
+ System.err.println("applicationDidFailToRegisterForRemoteNotificationsWithError " + error);
+ }
}
Modified: iOSME/src/net/yura/ios/iOSImageEncoder.java
===================================================================
--- iOSME/src/net/yura/ios/iOSImageEncoder.java 2021-11-12 15:41:42 UTC (rev 2591)
+++ iOSME/src/net/yura/ios/iOSImageEncoder.java 2021-11-18 16:28:56 UTC (rev 2592)
@@ -31,7 +31,7 @@
try {
// TODO there must be a better way to do this
- byte[] bytes = data.bytes().getBytePtr().toByteArray((int)data.length());
+ byte[] bytes = iOSUtil.toBytes(data);
out.write(bytes);
}
catch (Exception ex) {
Added: iOSME/src/net/yura/ios/iOSNotifications.java
===================================================================
--- iOSME/src/net/yura/ios/iOSNotifications.java (rev 0)
+++ iOSME/src/net/yura/ios/iOSNotifications.java 2021-11-18 16:28:56 UTC (rev 2592)
@@ -0,0 +1,132 @@
+package net.yura.ios;
+
+import java.util.Map;
+import javax.microedition.midlet.MIDlet;
+import apple.foundation.NSError;
+import apple.foundation.NSNumber;
+import apple.uikit.UIApplication;
+import apple.usernotifications.UNMutableNotificationContent;
+import apple.usernotifications.UNNotification;
+import apple.usernotifications.UNNotificationContent;
+import apple.usernotifications.UNNotificationRequest;
+import apple.usernotifications.UNNotificationResponse;
+import apple.usernotifications.UNNotificationSettings;
+import apple.usernotifications.UNTimeIntervalNotificationTrigger;
+import apple.usernotifications.UNUserNotificationCenter;
+import apple.usernotifications.enums.UNAuthorizationOptions;
+import apple.usernotifications.enums.UNAuthorizationStatus;
+import apple.usernotifications.enums.UNNotificationPresentationOptions;
+import apple.usernotifications.enums.UNNotificationSetting;
+import apple.usernotifications.protocol.UNUserNotificationCenterDelegate;
+
+public class iOSNotifications implements UNUserNotificationCenterDelegate {
+
+ public static void init() {
+ // this is needed to allow showing local notifications when the app is in foreground.
+ UNUserNotificationCenter notificationCenter = UNUserNotificationCenter.currentNotificationCenter();
+ notificationCenter.setDelegate(new iOSNotifications());
+ }
+
+ /**
+ * this is needed to allow local notifications to display when the app is already open (in foreground)
+ */
+ @Override
+ public void userNotificationCenterWillPresentNotificationWithCompletionHandler(UNUserNotificationCenter center, UNNotification notification, Block_userNotificationCenterWillPresentNotificationWithCompletionHandler completionHandler) {
+ completionHandler.call_userNotificationCenterWillPresentNotificationWithCompletionHandler(UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Badge);
+ }
+
+ @Override
+ public void userNotificationCenterDidReceiveNotificationResponseWithCompletionHandler(UNUserNotificationCenter center, UNNotificationResponse response, Block_userNotificationCenterDidReceiveNotificationResponseWithCompletionHandler completionHandler) {
+ // Handle the actions for the notification
+
+ UNNotificationContent content = response.notification().request().content();
+ Map<?, ?> customParams = content.userInfo();
+
+ System.out.println("notification with extras " + customParams);
+ //String queryString = Url.toQueryString(new Hashtable(customParams));
+
+ try {
+ MIDlet.DEFAULT_MIDLET.openNotification(customParams);
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ // user clicked on notification, we can clear the app icon badge
+ UIApplication.sharedApplication().setApplicationIconBadgeNumber(0);
+ }
+
+ public static void requestNotificationAuthorization() {
+ UNUserNotificationCenter notificationCenter = UNUserNotificationCenter.currentNotificationCenter();
+ notificationCenter.requestAuthorizationWithOptionsCompletionHandler(UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
+ new UNUserNotificationCenter.Block_requestAuthorizationWithOptionsCompletionHandler() {
+ @Override
+ public void call_requestAuthorizationWithOptionsCompletionHandler(boolean granted, NSError error) {
+ System.out.println("notificationCenter.requestAuthorization " + granted + " " + error);
+
+ UIApplication app = UIApplication.sharedApplication();
+ app.registerForRemoteNotifications();
+ }
+ });
+ }
+
+ public static void showNotification(String title, String message, int num, Map<?,?> extras) {
+ //requestNotificationAuthorization();
+ //checkNotificationAuthorization();
+
+ UNUserNotificationCenter notificationCenter = UNUserNotificationCenter.currentNotificationCenter();
+
+ UNMutableNotificationContent content = UNMutableNotificationContent.alloc().init();
+ content.setTitle(title);
+ content.setBody(message);
+ //content.setSound(UNNotificationSound.defaultSound());
+ content.setBadge(NSNumber.numberWithLong(UIApplication.sharedApplication().applicationIconBadgeNumber() + num));
+
+ content.setUserInfo(iOSUtil.toNSDictionary(extras));
+
+ UNTimeIntervalNotificationTrigger trigger = UNTimeIntervalNotificationTrigger.triggerWithTimeIntervalRepeats(0.1, false);
+
+ String uuidString = "notificationIdentifier"; //UUID.randomUUID().toString();
+ UNNotificationRequest request = UNNotificationRequest.requestWithIdentifierContentTrigger(uuidString, content, trigger);
+
+ notificationCenter.addNotificationRequestWithCompletionHandler(request, new UNUserNotificationCenter.Block_addNotificationRequestWithCompletionHandler() {
+ @Override
+ public void call_addNotificationRequestWithCompletionHandler(NSError error) {
+ if (error != null) {
+ System.out.println("error showing notification " + error);
+ }
+ else {
+ System.out.println("showing notification");
+ }
+ }
+ });
+ }
+
+ public static void checkNotificationAuthorization() {
+
+ UNUserNotificationCenter notificationCenter = UNUserNotificationCenter.currentNotificationCenter();
+ notificationCenter.getNotificationSettingsWithCompletionHandler(new UNUserNotificationCenter.Block_getNotificationSettingsWithCompletionHandler() {
+ @Override
+ public void call_getNotificationSettingsWithCompletionHandler(UNNotificationSettings settings) {
+ if (settings.authorizationStatus() == UNAuthorizationStatus.Authorized || settings.authorizationStatus() == UNAuthorizationStatus.Provisional) {
+
+ if (settings.alertSetting() == UNNotificationSetting.Enabled) {
+ // Schedule an alert-only notification.
+ System.out.println("Schedule an alert-only notification.");
+ }
+ else {
+ // Schedule a notification with a badge and sound.
+ System.out.println("Schedule a notification with a badge and sound.");
+ }
+ }
+ else if (settings.authorizationStatus() == UNAuthorizationStatus.NotDetermined) {
+ // not determined, ask user for permission now
+ System.out.println("not determined, ask user for permission now");
+ }
+ else { // Denied or Ephemeral
+ System.out.println("notifications not allowed");
+ }
+ }
+ });
+ }
+}
Added: iOSME/src/net/yura/ios/iOSUtil.java
===================================================================
--- iOSME/src/net/yura/ios/iOSUtil.java (rev 0)
+++ iOSME/src/net/yura/ios/iOSUtil.java 2021-11-18 16:28:56 UTC (rev 2592)
@@ -0,0 +1,27 @@
+package net.yura.ios;
+
+import java.util.Map;
+import apple.foundation.NSData;
+import apple.foundation.NSDictionary;
+import apple.foundation.NSMutableDictionary;
+
+public class iOSUtil {
+
+ /**
+ * Convert a Java [Map] to [NSDictionary].
+ */
+ public static <K, V> NSDictionary<K, V> toNSDictionary(Map<K, V> map) {
+ NSMutableDictionary dict = NSMutableDictionary.alloc().initWithCapacity(map.size());
+ //map.forEach(dict::put);
+
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ dict.put(entry.getKey(), entry.getValue());
+ }
+
+ return dict;
+ }
+
+ public static byte[] toBytes(NSData data) {
+ return data.bytes().getBytePtr().toByteArray((int)data.length());
+ }
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|