# HG changeset patch # User bsmith@81767d24-ef19-dc11-ae90-00e081727c95 # Date 1620588443 0 # Node ID 66c490aa719dcf1ffc8dd6324917d3f2dbdab207 # Parent 5f92284e2b0852f7ab66c11c258ab61193571453 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. diff -r 5f92284e2b08 -r 66c490aa719d android/DWindows.kt --- 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?, diff -r 5f92284e2b08 -r 66c490aa719d android/dw.cpp --- 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; }