diff --git a/doc/source/services.rst b/doc/source/services.rst index b5519a5cb7..8b5d9a7851 100644 --- a/doc/source/services.rst +++ b/doc/source/services.rst @@ -107,6 +107,14 @@ the json module to encode and decode more complex data. from os import environ argument = environ.get('PYTHON_SERVICE_ARGUMENT', '') + +To customize the notification icon, title, and text use three optional +arguments to service.start():: + + service.start(mActivity, 'small_icon', 'title', 'content' , argument) + +Where 'small_icon' is the name of an Android drawable or mipmap resource, +and 'title' and 'content' are strings in the notification. Services support a range of options and interactions not yet documented here but all accessible via calling other methods of the diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java index dd6f307ec7..cb89e3bdcd 100644 --- a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java +++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java @@ -102,30 +102,47 @@ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) protected void doStartForeground(Bundle extras) { String serviceTitle = extras.getString("serviceTitle"); - String serviceDescription = extras.getString("serviceDescription"); + String smallIconName = extras.getString("smallIconName"); + String contentTitle = extras.getString("contentTitle"); + String contentText = extras.getString("contentText"); Notification notification; Context context = getApplicationContext(); Intent contextIntent = new Intent(context, PythonActivity.class); PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // Unspecified icon uses default. + int smallIconId = context.getApplicationInfo().icon; + if (!smallIconName.equals("")){ + int resId = getResources().getIdentifier(smallIconName, "mipmap", + getPackageName()); + if (resId ==0) { + resId = getResources().getIdentifier(smallIconName, "drawable", + getPackageName()); + } + if (resId !=0) { + smallIconId = resId; + } + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // This constructor is deprecated notification = new Notification( - context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + smallIconId, serviceTitle, System.currentTimeMillis()); try { // prevent using NotificationCompat, this saves 100kb on apk Method func = notification.getClass().getMethod( "setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class); - func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + func.invoke(notification, context, contentTitle, contentText, pIntent); } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { } } else { // for android 8+ we need to create our own channel // https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1 - String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a"; //TODO: make this configurable - String channelName = "Background Service"; //TODO: make this configurable + String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a" + getServiceId(); + String channelName = "Background Service" + getServiceId(); NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE); @@ -135,10 +152,10 @@ protected void doStartForeground(Bundle extras) { manager.createNotificationChannel(chan); Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID); - builder.setContentTitle(serviceTitle); - builder.setContentText(serviceDescription); + builder.setContentTitle(contentTitle); + builder.setContentText(contentText); builder.setContentIntent(pIntent); - builder.setSmallIcon(context.getApplicationInfo().icon); + builder.setSmallIcon(smallIconId); notification = builder.build(); } startForeground(getServiceId(), notification); diff --git a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java index de84ac42bf..9406f91d89 100644 --- a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java @@ -17,31 +17,49 @@ public int startType() { protected int getServiceId() { return {{ service_id }}; } + + static private void _start(Context ctx, String smallIconName, + String contentTitle, String contentText, + String pythonServiceArgument) { + Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle, + contentText, pythonServiceArgument); + ctx.startService(intent); + } static public void start(Context ctx, String pythonServiceArgument) { - Intent intent = getDefaultIntent(ctx, pythonServiceArgument); - ctx.startService(intent); + _start(ctx, "", "{{ args.name }}", "{{ name|capitalize }}", pythonServiceArgument); } - static public Intent getDefaultIntent(Context ctx, String pythonServiceArgument) { + static public void start(Context ctx, String smallIconName, + String contentTitle, String contentText, + String pythonServiceArgument) { + _start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument); + } + + static public Intent getDefaultIntent(Context ctx, String smallIconName, + String contentTitle, String contentText, + String pythonServiceArgument) { Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); String argument = ctx.getFilesDir().getAbsolutePath() + "/app"; intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath()); intent.putExtra("androidArgument", argument); intent.putExtra("serviceTitle", "{{ args.name }}"); - intent.putExtra("serviceDescription", "{{ name|capitalize }}"); intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); intent.putExtra("pythonName", "{{ name }}"); intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}"); intent.putExtra("pythonHome", argument); intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); intent.putExtra("pythonServiceArgument", pythonServiceArgument); + intent.putExtra("smallIconName", smallIconName); + intent.putExtra("contentTitle", contentTitle); + intent.putExtra("contentText", contentText); return intent; } @Override protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) { - return Service{{ name|capitalize }}.getDefaultIntent(ctx, pythonServiceArgument); + return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "", + pythonServiceArgument); } static public void stop(Context ctx) { diff --git a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java index f1eaf0702d..ff889b462c 100644 --- a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java @@ -34,10 +34,13 @@ public static void prepare(Context ctx) { PythonUtil.unpackAsset(ctx, "private", app_root_file, true); PythonUtil.unpackPyBundle(ctx, ctx.getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false); } - - public static void start(Context ctx, String pythonServiceArgument) { - Intent intent = getDefaultIntent(ctx, pythonServiceArgument); - + + static private void _start(Context ctx, String smallIconName, + String contentTitle, + String contentText, + String pythonServiceArgument) { + Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle, + contentText, pythonServiceArgument); //foreground: {{foreground}} {% if foreground %} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -50,26 +53,43 @@ public static void start(Context ctx, String pythonServiceArgument) { {% endif %} } - static public Intent getDefaultIntent(Context ctx, String pythonServiceArgument) { + public static void start(Context ctx, String pythonServiceArgument) { + _start(ctx, "", "{{ args.name }}", "{{ name|capitalize }}", pythonServiceArgument); + } + + static public void start(Context ctx, String smallIconName, + String contentTitle, + String contentText, + String pythonServiceArgument) { + _start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument); + } + + static public Intent getDefaultIntent(Context ctx, String smallIconName, + String contentTitle, + String contentText, + String pythonServiceArgument) { String appRoot = PythonUtil.getAppRoot(ctx); Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); intent.putExtra("androidPrivate", appRoot); intent.putExtra("androidArgument", appRoot); intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); intent.putExtra("serviceTitle", "{{ name|capitalize }}"); - intent.putExtra("serviceDescription", ""); intent.putExtra("pythonName", "{{ name }}"); intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}"); intent.putExtra("pythonHome", appRoot); intent.putExtra("androidUnpack", appRoot); intent.putExtra("pythonPath", appRoot + ":" + appRoot + "/lib"); intent.putExtra("pythonServiceArgument", pythonServiceArgument); + intent.putExtra("smallIconName", smallIconName); + intent.putExtra("contentTitle", contentTitle); + intent.putExtra("contentText", contentText); return intent; } @Override protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) { - return Service{{ name|capitalize }}.getDefaultIntent(ctx, pythonServiceArgument); + return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "", + pythonServiceArgument); } diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java index 598549d345..eeda810bef 100644 --- a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java @@ -34,16 +34,40 @@ public static void start(Context ctx, String pythonServiceArgument) { intent.putExtra("androidArgument", argument); intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); intent.putExtra("serviceTitle", "{{ name|capitalize }}"); - intent.putExtra("serviceDescription", ""); intent.putExtra("pythonName", "{{ name }}"); intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}"); intent.putExtra("pythonHome", argument); intent.putExtra("androidUnpack", argument); intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); intent.putExtra("pythonServiceArgument", pythonServiceArgument); + intent.putExtra("smallIconName", ""); + intent.putExtra("contentTitle", "{{ name|capitalize }}"); + intent.putExtra("contentText", ""); ctx.startService(intent); } - + + public static void start(Context ctx, String smallIconName, + String contentTitle, + String contentText, + String pythonServiceArgument) { + String argument = ctx.getFilesDir().getAbsolutePath() + "/app"; + Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); + intent.putExtra("androidPrivate", argument); + intent.putExtra("androidArgument", argument); + intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); + intent.putExtra("serviceTitle", "{{ name|capitalize }}"); + intent.putExtra("pythonName", "{{ name }}"); + intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}"); + intent.putExtra("pythonHome", argument); + intent.putExtra("androidUnpack", argument); + intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); + intent.putExtra("pythonServiceArgument", pythonServiceArgument); + intent.putExtra("smallIconName", smallIconName); + intent.putExtra("contentTitle", contentTitle); + intent.putExtra("contentText", contentText); + ctx.startService(intent); + } + public static void stop(Context ctx) { Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); ctx.stopService(intent);