changeset 2522:66c490aa719d

Android: Implement notifications, images on notifications incomplete. Also detect the Android application ID, and generate one if not set or detected. Also store the path as the application directory.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Sun, 09 May 2021 19:27:23 +0000
parents 5f92284e2b08
children 82cdb3ad7c25
files android/DWindows.kt android/dw.cpp
diffstat 2 files changed, 144 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/android/DWindows.kt	Sun May 09 09:31:14 2021 +0000
+++ b/android/DWindows.kt	Sun May 09 19:27:23 2021 +0000
@@ -1,6 +1,8 @@
 package org.dbsoft.dwindows
 
 import android.R.attr
+import android.app.NotificationChannel
+import android.app.NotificationManager
 import android.content.ClipData
 import android.content.ClipboardManager
 import android.content.Context
@@ -12,10 +14,7 @@
 import android.graphics.drawable.GradientDrawable
 import android.media.AudioManager
 import android.media.ToneGenerator
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.os.MessageQueue
+import android.os.*
 import android.text.InputFilter
 import android.text.InputFilter.LengthFilter
 import android.text.InputType
@@ -38,6 +37,8 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.AppCompatEditText
 import androidx.collection.SimpleArrayMap
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
 import androidx.recyclerview.widget.RecyclerView
 import androidx.viewpager2.widget.ViewPager2
 import com.google.android.material.tabs.TabLayout
@@ -240,6 +241,7 @@
     var windowLayout: LinearLayout? = null
     var threadLock = ReentrantLock()
     var threadCond = threadLock.newCondition()
+    var notificationID: Int = 0
 
     // Our version of runOnUiThread that waits for execution
     fun waitOnUiThread(runnable: Runnable)
@@ -279,7 +281,7 @@
 
         // Initialize the Dynamic Windows code...
         // This will start a new thread that calls the app's dwmain()
-        dwindowsInit(s)
+        dwindowsInit(s, this.getPackageName())
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
@@ -1625,11 +1627,52 @@
         }
     }
 
+    fun dwInit(appid: String, appname: String)
+    {
+        waitOnUiThread {
+            // Create the notification channel in dw_init()
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                // Create the NotificationChannel
+                val importance = NotificationManager.IMPORTANCE_DEFAULT
+                val mChannel = NotificationChannel(appid, appname, importance)
+                // Register the channel with the system; you can't change the importance
+                // or other notification behaviors after this
+                val notificationManager =
+                    getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+                notificationManager.createNotificationChannel(mChannel)
+            }
+        }
+    }
+
+    fun notificationNew(title: String, imagepath: String, text: String, appid: String): NotificationCompat.Builder?
+    {
+        var builder: NotificationCompat.Builder? = null
+
+        waitOnUiThread {
+            builder = NotificationCompat.Builder(this, appid)
+                .setContentTitle(title)
+                .setContentText(text)
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+        }
+        return builder
+    }
+
+    fun notificationSend(builder: NotificationCompat.Builder)
+    {
+        waitOnUiThread {
+            notificationID += 1
+            with(NotificationManagerCompat.from(this)) {
+                // notificationId is a unique int for each notification that you must define
+                notify(notificationID, builder.build())
+            }
+        }
+    }
+
     /*
      * Native methods that are implemented by the 'dwindows' native library,
      * which is packaged with this application.
      */
-    external fun dwindowsInit(dataDir: String)
+    external fun dwindowsInit(dataDir: String, appid: String)
     external fun eventHandler(
         obj1: View?,
         obj2: View?,
--- a/android/dw.cpp	Sun May 09 09:31:14 2021 +0000
+++ b/android/dw.cpp	Sun May 09 19:27:23 2021 +0000
@@ -36,6 +36,10 @@
 
 #define DW_CLASS_NAME "org/dbsoft/dwindows/DWindows"
 
+static char _dw_app_id[_DW_APP_ID_SIZE+1]= {0};
+static char _dw_app_name[_DW_APP_ID_SIZE+1]= {0};
+static char _dw_exec_dir[MAX_PATH+1] = {0};
+
 static pthread_key_t _dw_env_key;
 static HEV _dw_main_event;
 static JavaVM *_dw_jvm;
@@ -110,9 +114,10 @@
  *      path: The path to the Android app.
  */
 JNIEXPORT void JNICALL
-Java_org_dbsoft_dwindows_DWindows_dwindowsInit(JNIEnv* env, jobject obj, jstring path)
-{
-    char *arg = strdup(env->GetStringUTFChars((jstring) path, NULL));
+Java_org_dbsoft_dwindows_DWindows_dwindowsInit(JNIEnv* env, jobject obj, jstring path, jstring appID)
+{
+    char *arg = strdup(env->GetStringUTFChars((jstring)path, NULL));
+    const char *appid = env->GetStringUTFChars((jstring)appID, NULL);
 
     if(!_dw_main_event)
     {
@@ -127,6 +132,17 @@
         _dw_main_event = dw_event_new();
     }
 
+    if(arg)
+    {
+        /* Store the passed in path for dw_app_dir() */
+        strncpy(_dw_exec_dir, arg, MAX_PATH);
+    }
+    if(appid)
+    {
+        /* Store our reported Android AppID */
+        strncpy(_dw_app_id, appid, _DW_APP_ID_SIZE);
+    }
+
     /* Launch the new thread to execute dwmain() */
     dw_thread_new((void *) _dw_main_launch, arg, 0);
 }
@@ -628,11 +644,7 @@
  */
 char * API dw_app_dir(void)
 {
-    static char _dw_exec_dir[MAX_PATH+1] = {0};
-    /* Code to determine the execution directory here,
-     * some implementations make this variable global
-     * and determine the location in dw_init().
-     */
+    /* The path is passed in via JNI dwindowsInit() */
     return _dw_exec_dir;
 }
 
@@ -649,12 +661,16 @@
  *          This must be called before dw_init().  If dw_init() is called first
  *          it will create a unique ID in the form: org.dbsoft.dwindows.application
  *          or if the application name cannot be detected: org.dbsoft.dwindows.pid.#
- *          The appname is only required on Windows.  If NULL is passed the detected
- *          application name will be used, but a prettier name may be desired.
+ *          The appname is used on Windows and Android.  If NULL is passed the
+ *          detected name will be used, but a prettier name may be desired.
  */
 int API dw_app_id_set(const char *appid, const char *appname)
 {
-    return DW_ERROR_UNKNOWN;
+    if(appid)
+        strncpy(_dw_app_id, appid, _DW_APP_ID_SIZE);
+    if(appname)
+        strncpy(_dw_app_name, appname, _DW_APP_ID_SIZE);
+    return DW_ERROR_NONE;
 }
 
 /*
@@ -4237,7 +4253,7 @@
  */
 void API dw_environment_query(DWEnv *env)
 {
-    strcpy(env->osName, "Unknown");
+    strcpy(env->osName, "Android");
 
     strcpy(env->buildDate, __DATE__);
     strcpy(env->buildTime, __TIME__);
@@ -5380,6 +5396,27 @@
  */
 int API dw_init(int newthread, int argc, char *argv[])
 {
+    JNIEnv *env;
+
+    if(!_dw_app_id[0])
+    {
+        /* Generate an Application ID based on the PID if all else fails. */
+        snprintf(_dw_app_id, _DW_APP_ID_SIZE, "%s.pid.%d", DW_APP_DOMAIN_DEFAULT, getpid());
+    }
+
+    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
+    {
+        // Construct a String
+        jstring appid = env->NewStringUTF(_dw_app_id);
+        jstring appname = env->NewStringUTF(_dw_app_name);
+        // First get the class that contains the method you need to call
+        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
+        // Get the method that you want to call
+        jmethodID dwInit = env->GetMethodID(clazz, "dwInit",
+                                            "(Ljava/lang/String;Ljava/lang/String;)V");
+        // Call the method on the object
+        env->CallVoidMethod(_dw_obj, dwInit, appid, appname);
+    }
     return DW_ERROR_NONE;
 }
 
@@ -5484,6 +5521,39 @@
  */
 HWND API dw_notification_new(const char *title, const char *imagepath, const char *description, ...)
 {
+    JNIEnv *env;
+
+    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
+    {
+        // Construct a String
+        jstring appid = env->NewStringUTF(_dw_app_id);
+        jstring ntitle = env->NewStringUTF(title);
+        jstring ndesc = NULL;
+        jstring image = NULL;
+
+        if(description)
+        {
+            va_list args;
+            char outbuf[1025] = {0};
+
+            va_start(args, description);
+            vsnprintf(outbuf, 1024, description, args);
+            va_end(args);
+
+            ndesc = env->NewStringUTF(outbuf);
+        }
+        if(imagepath)
+            image = env->NewStringUTF(imagepath);
+
+        // First get the class that contains the method you need to call
+        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
+        // Get the method that you want to call
+        jmethodID notificationNew = env->GetMethodID(clazz, "notificationNew",
+                                                     "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroidx/core/app/NotificationCompat$Builder;");
+        // Call the method on the object
+        jobject result = env->NewWeakGlobalRef(env->CallObjectMethod(_dw_obj, notificationNew, ntitle, image, ndesc, appid));
+        return result;
+    }
     return 0;
 }
 
@@ -5496,6 +5566,19 @@
  */
 int API dw_notification_send(HWND notification)
 {
+    JNIEnv *env;
+
+    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
+    {
+        // First get the class that contains the method you need to call
+        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
+        // Get the method that you want to call
+        jmethodID notificationNew = env->GetMethodID(clazz, "notificationSend",
+                                                     "(Landroidx/core/app/NotificationCompat$Builder;)V");
+        // Call the method on the object
+        env->CallVoidMethod(_dw_obj, notificationNew, notification);
+        return DW_ERROR_NONE;
+    }
     return DW_ERROR_UNKNOWN;
 }