diff --git a/.gitignore b/.gitignore
index 0235c648c..5d1edaf6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
/src/windows/**/bin
/src/windows/**/obj
.idea
-src/cordova-plugin-local-notifications.iml
\ No newline at end of file
+src/cordova-plugin-local-notifications.iml
+.DS_Store
diff --git a/plugin.xml b/plugin.xml
index 8dd76a36a..e975b8ffe 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -93,6 +93,11 @@
+
+
+
+
+
@@ -100,7 +105,7 @@
-
+
-
+ android:exported="false"
+ android:theme="@style/Theme.AppCompat.NoActionBar"/>
-
+
+
+
+
+
+
+
+
@@ -207,6 +224,10 @@
src="src/android/notification/util/AssetUtil.java"
target-dir="src/de/appplant/cordova/plugin/notification/util" />
+
+
@@ -219,6 +240,10 @@
src="src/android/notification/Notification.java"
target-dir="src/de/appplant/cordova/plugin/notification" />
+
+
diff --git a/src/android/ClearReceiver.java b/src/android/ClearReceiver.java
index c4e771c91..55ed849cc 100644
--- a/src/android/ClearReceiver.java
+++ b/src/android/ClearReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
diff --git a/src/android/ClickReceiver.java b/src/android/ClickReceiver.java
index 404fd0633..eae916157 100644
--- a/src/android/ClickReceiver.java
+++ b/src/android/ClickReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -22,13 +23,14 @@
package de.appplant.cordova.plugin.localnotification;
import android.os.Bundle;
-import android.support.v4.app.RemoteInput;
+import androidx.core.app.RemoteInput;
import org.json.JSONException;
import org.json.JSONObject;
import de.appplant.cordova.plugin.notification.Notification;
import de.appplant.cordova.plugin.notification.receiver.AbstractClickReceiver;
+import de.appplant.cordova.plugin.notification.util.LaunchUtils;
import static de.appplant.cordova.plugin.localnotification.LocalNotification.fireEvent;
import static de.appplant.cordova.plugin.notification.Options.EXTRA_LAUNCH;
@@ -95,7 +97,7 @@ private void launchAppIf() {
if (!doLaunch)
return;
- launchApp();
+ LaunchUtils.launchApp(getApplicationContext());
}
/**
diff --git a/src/android/LocalNotification.java b/src/android/LocalNotification.java
index e02f477f7..27e399413 100644
--- a/src/android/LocalNotification.java
+++ b/src/android/LocalNotification.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -26,9 +27,26 @@
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.KeyguardManager;
+import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.provider.Settings;
import android.util.Pair;
import android.view.View;
+// Notification permission
+
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import androidx.core.app.NotificationCompat;
+import android.content.Intent;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
@@ -43,22 +61,30 @@
import java.util.ArrayList;
import java.util.List;
+import javax.security.auth.callback.Callback;
+
import de.appplant.cordova.plugin.notification.Manager;
import de.appplant.cordova.plugin.notification.Notification;
import de.appplant.cordova.plugin.notification.Options;
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.action.ActionGroup;
+import static android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
+import static android.content.Context.POWER_SERVICE;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.M;
import static de.appplant.cordova.plugin.notification.Notification.Type.SCHEDULED;
import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED;
+// import com.getcapacitor.CapacitorWebView;
+
/**
* This plugin utilizes the Android AlarmManager in combination with local
* notifications. When a local notification is scheduled the alarm manager takes
* care of firing the event. When the event is processed, a notification is put
* in the Android notification center and status bar.
*/
-@SuppressWarnings({"Convert2Diamond", "Convert2Lambda"})
+@SuppressWarnings({ "Convert2Diamond", "Convert2Lambda" })
public class LocalNotification extends CordovaPlugin {
// Reference to the web view for static access
@@ -73,13 +99,19 @@ public class LocalNotification extends CordovaPlugin {
// Launch details
private static Pair launchDetails;
+ private static int REQUEST_PERMISSIONS_CALL = 10;
+
+ private static int REQUEST_IGNORE_BATTERY_CALL = 20;
+
+ private CallbackContext callbackContext;
+
/**
- * Called after plugin construction and fields have been initialized.
- * Prefer to use pluginInitialize instead since there is no value in
- * having parameters on the initialize() function.
+ * Called after plugin construction and fields have been initialized. Prefer to
+ * use pluginInitialize instead since there is no value in having parameters on
+ * the initialize() function.
*/
@Override
- public void initialize (CordovaInterface cordova, CordovaWebView webView) {
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
LocalNotification.webView = new WeakReference(webView);
}
@@ -89,7 +121,7 @@ public void initialize (CordovaInterface cordova, CordovaWebView webView) {
* @param multitasking Flag indicating if multitasking is turned on for app.
*/
@Override
- public void onResume (boolean multitasking) {
+ public void onResume(boolean multitasking) {
super.onResume(multitasking);
deviceready();
}
@@ -105,23 +137,20 @@ public void onDestroy() {
/**
* Executes the request.
*
- * This method is called from the WebView thread. To do a non-trivial
- * amount of work, use:
- * cordova.getThreadPool().execute(runnable);
+ * This method is called from the WebView thread. To do a non-trivial amount of
+ * work, use: cordova.getThreadPool().execute(runnable);
*
- * To run on the UI thread, use:
- * cordova.getActivity().runOnUiThread(runnable);
+ * To run on the UI thread, use: cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*
* @return Whether the action was valid.
*/
@Override
- public boolean execute (final String action, final JSONArray args,
- final CallbackContext command) throws JSONException {
+ public boolean execute(final String action, final JSONArray args, final CallbackContext command)
+ throws JSONException {
if (action.equals("launch")) {
launch(command);
@@ -132,45 +161,42 @@ public boolean execute (final String action, final JSONArray args,
public void run() {
if (action.equals("ready")) {
deviceready();
- } else
- if (action.equals("check")) {
+ } else if (action.equals("check")) {
check(command);
- } else
- if (action.equals("request")) {
+ } else if (action.equals("request")) {
request(command);
- } else
- if (action.equals("actions")) {
+ } else if (action.equals("actions")) {
actions(args, command);
- } else
- if (action.equals("schedule")) {
+ } else if (action.equals("schedule")) {
schedule(args, command);
- } else
- if (action.equals("update")) {
+ } else if (action.equals("update")) {
update(args, command);
- } else
- if (action.equals("cancel")) {
+ } else if (action.equals("cancel")) {
cancel(args, command);
- } else
- if (action.equals("cancelAll")) {
+ } else if (action.equals("cancelAll")) {
cancelAll(command);
- } else
- if (action.equals("clear")) {
+ } else if (action.equals("clear")) {
clear(args, command);
- } else
- if (action.equals("clearAll")) {
+ } else if (action.equals("clearAll")) {
clearAll(command);
- } else
- if (action.equals("type")) {
+ } else if (action.equals("type")) {
type(args, command);
- } else
- if (action.equals("ids")) {
+ } else if (action.equals("ids")) {
ids(args, command);
- } else
- if (action.equals("notification")) {
+ } else if (action.equals("notification")) {
notification(args, command);
- } else
- if (action.equals("notifications")) {
+ } else if (action.equals("notifications")) {
notifications(args, command);
+ } else if (action.equals("hasDoNotDisturbPermissions")) {
+ hasDoNotDisturbPermissions(command);
+ } else if (action.equals("requestDoNotDisturbPermissions")) {
+ requestDoNotDisturbPermissions(command);
+ } else if (action.equals("isIgnoringBatteryOptimizations")) {
+ isIgnoringBatteryOptimizations(command);
+ } else if (action.equals("requestIgnoreBatteryOptimizations")) {
+ requestIgnoreBatteryOptimizations(command);
+ } else if (action.equals("dummyNotifications")) {
+ dummyNotifications(command);
}
}
});
@@ -179,11 +205,189 @@ public void run() {
}
/**
- * Set launchDetails object.
+ * required for android 13 to get the runtime notification permissions
+ *
*
* @param command The callback context used when calling back into
* JavaScript.
*/
+ private void dummyNotifications(CallbackContext command) {
+
+ fireEvent("dummyNotifications");
+ NotificationManager mNotificationManager;
+ NotificationCompat.Builder mBuilder;
+ String NOTIFICATION_CHANNEL_ID = "10004457";
+ String notificationMsg = "Test";
+ String notificationTitle = "Mdd";
+ Context context = cordova.getActivity().getApplicationContext();
+
+ Intent intentToLaunch = new Intent(context, TriggerReceiver.class);
+ intentToLaunch.putExtra("Callfrom", "reminders");
+
+ final PendingIntent resultPendingIntent = PendingIntent.getActivity(context,
+ 0, intentToLaunch, PendingIntent.FLAG_IMMUTABLE);
+
+ mBuilder = new NotificationCompat.Builder(context,NOTIFICATION_CHANNEL_ID);
+
+ mBuilder.setContentIntent(resultPendingIntent);
+
+ mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O)
+ {
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "NOTIFICATION_CHANNEL_NAME", importance);
+ assert mNotificationManager != null;
+ mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
+ mNotificationManager.createNotificationChannel(notificationChannel);
+ }
+
+ command.success();
+ }
+
+ /**
+ * Determine if do not disturb permissions have been granted
+ *
+ * @return true if we still need to acquire do not disturb permissions.
+ */
+ private boolean needsDoNotDisturbPermissions() {
+ Context mContext = this.cordova.getActivity().getApplicationContext();
+
+ NotificationManager mNotificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ return SDK_INT >= M && !mNotificationManager.isNotificationPolicyAccessGranted();
+ }
+
+ /**
+ * Determine if we have do not disturb permissions.
+ *
+ * @param command callback context. Returns with true if the we have
+ * permissions, false if we do not.
+ */
+ private void hasDoNotDisturbPermissions(CallbackContext command) {
+ success(command, !needsDoNotDisturbPermissions());
+ }
+
+ /**
+ * Launch an activity to request do not disturb permissions
+ *
+ * @param command callback context. Returns with results of
+ * hasDoNotDisturbPermissions after the activity is closed.
+ */
+ private void requestDoNotDisturbPermissions(CallbackContext command) {
+ if (needsDoNotDisturbPermissions()) {
+ this.callbackContext = command;
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
+ pluginResult.setKeepCallback(true); // Keep callback
+ command.sendPluginResult(pluginResult);
+
+ Intent intent = new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+
+ cordova.startActivityForResult(this, intent, REQUEST_PERMISSIONS_CALL);
+ return;
+ }
+ success(command, true);
+ }
+
+ /**
+ * Determine if do not battery optimization permissions have been granted
+ *
+ * @return true if we are succcessfully ignoring battery permissions.
+ */
+ private boolean ignoresBatteryOptimizations() {
+ Context mContext = this.cordova.getActivity().getApplicationContext();
+ PowerManager pm = (PowerManager) mContext.getSystemService(POWER_SERVICE);
+
+ return SDK_INT <= M || pm.isIgnoringBatteryOptimizations(mContext.getPackageName());
+ }
+
+ /**
+ * Determine if we have do not disturb permissions.
+ *
+ * @param command callback context. Returns with true if the we have
+ * permissions, false if we do not.
+ */
+ private void isIgnoringBatteryOptimizations(CallbackContext command) {
+ success(command, ignoresBatteryOptimizations());
+ }
+
+ /**
+ * Launch an activity to request do not disturb permissions
+ *
+ * @param command callback context. Returns with results of
+ * hasDoNotDisturbPermissions after the activity is closed.
+ */
+ private void requestIgnoreBatteryOptimizations(CallbackContext command) {
+ if (!ignoresBatteryOptimizations()) {
+ this.callbackContext = command;
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
+ pluginResult.setKeepCallback(true); // Keep callback
+ command.sendPluginResult(pluginResult);
+
+ String packageName = this.cordova.getContext().getPackageName();
+ String action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
+
+ // use the generic intent if we don't have access to request ignore permissions
+ // directly
+ // User can add "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" to the manifest, but
+ // risks having the app banned.
+ try {
+ PackageManager packageManager = this.cordova.getContext().getPackageManager();
+ PackageInfo pi = packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+
+ for (int i = 0; i < pi.requestedPermissions.length; ++i) {
+ if (pi.requestedPermissions[i].equals(REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) {
+ action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // leave action as default if package not found
+ }
+
+ try {
+ Intent intent = new Intent(action);
+
+ intent.setData(Uri.parse("package:" + packageName));
+
+ cordova.startActivityForResult(this, intent, REQUEST_IGNORE_BATTERY_CALL);
+ } catch (ActivityNotFoundException e) {
+ // could not find the generic ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
+ // and did not have access to launch REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+ // Fallback to just figuring out if battery optimizations are removed (probably
+ // not)
+ // since we can't ask the user to set it, because we can't launch an activity.
+ isIgnoringBatteryOptimizations(command);
+ this.callbackContext = null;
+ }
+
+ return;
+ }
+ success(command, true);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_PERMISSIONS_CALL && this.callbackContext != null) {
+ hasDoNotDisturbPermissions(this.callbackContext);
+
+ // clean up callback context.
+ this.callbackContext = null;
+ } else if (requestCode == REQUEST_IGNORE_BATTERY_CALL && this.callbackContext != null) {
+ isIgnoringBatteryOptimizations(this.callbackContext);
+
+ this.callbackContext = null;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ /**
+ * Set launchDetails object.
+ *
+ * @param command The callback context used when calling back into JavaScript.
+ */
@SuppressLint("DefaultLocale")
private void launch(CallbackContext command) {
if (launchDetails == null)
@@ -206,21 +410,19 @@ private void launch(CallbackContext command) {
/**
* Ask if user has enabled permission for local notifications.
*
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void check (CallbackContext command) {
- boolean allowed = getNotMgr().getNotCompMgr().areNotificationsEnabled();
+ private void check(CallbackContext command) {
+ boolean allowed = getNotMgr().hasPermission();
success(command, allowed);
}
/**
* Request permission for local notifications.
*
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void request (CallbackContext command) {
+ private void request(CallbackContext command) {
check(command);
}
@@ -228,29 +430,28 @@ private void request (CallbackContext command) {
* Register action group.
*
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void actions (JSONArray args, CallbackContext command) {
- int task = args.optInt(0);
- String id = args.optString(1);
- JSONArray list = args.optJSONArray(2);
+ private void actions(JSONArray args, CallbackContext command) {
+ int task = args.optInt(0);
+ String id = args.optString(1);
+ JSONArray list = args.optJSONArray(2);
Context context = cordova.getActivity();
switch (task) {
- case 0:
- ActionGroup group = ActionGroup.parse(context, id, list);
- ActionGroup.register(group);
- command.success();
- break;
- case 1:
- ActionGroup.unregister(id);
- command.success();
- break;
- case 2:
- boolean found = ActionGroup.isRegistered(id);
- success(command, found);
- break;
+ case 0:
+ ActionGroup group = ActionGroup.parse(context, id, list);
+ ActionGroup.register(group);
+ command.success();
+ break;
+ case 1:
+ ActionGroup.unregister(id);
+ command.success();
+ break;
+ case 2:
+ boolean found = ActionGroup.isRegistered(id);
+ success(command, found);
+ break;
}
}
@@ -258,16 +459,15 @@ private void actions (JSONArray args, CallbackContext command) {
* Schedule multiple local notifications.
*
* @param toasts The notifications to schedule.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void schedule (JSONArray toasts, CallbackContext command) {
+ private void schedule(JSONArray toasts, CallbackContext command) {
Manager mgr = getNotMgr();
for (int i = 0; i < toasts.length(); i++) {
- JSONObject dict = toasts.optJSONObject(i);
- Options options = new Options(dict);
- Request request = new Request(options);
+ JSONObject dict = toasts.optJSONObject(i);
+ Options options = new Options(dict);
+ Request request = new Request(options);
Notification toast = mgr.schedule(request, TriggerReceiver.class);
if (toast != null) {
@@ -282,15 +482,14 @@ private void schedule (JSONArray toasts, CallbackContext command) {
* Update multiple local notifications.
*
* @param updates Notification properties including their IDs.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void update (JSONArray updates, CallbackContext command) {
+ private void update(JSONArray updates, CallbackContext command) {
Manager mgr = getNotMgr();
for (int i = 0; i < updates.length(); i++) {
- JSONObject update = updates.optJSONObject(i);
- int id = update.optInt("id", 0);
+ JSONObject update = updates.optJSONObject(i);
+ int id = update.optInt("id", 0);
Notification toast = mgr.update(id, update, TriggerReceiver.class);
if (toast == null)
@@ -306,14 +505,13 @@ private void update (JSONArray updates, CallbackContext command) {
* Cancel multiple local notifications.
*
* @param ids Set of local notification IDs.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void cancel (JSONArray ids, CallbackContext command) {
+ private void cancel(JSONArray ids, CallbackContext command) {
Manager mgr = getNotMgr();
for (int i = 0; i < ids.length(); i++) {
- int id = ids.optInt(i, 0);
+ int id = ids.optInt(i, 0);
Notification toast = mgr.cancel(id);
if (toast == null)
@@ -328,8 +526,7 @@ private void cancel (JSONArray ids, CallbackContext command) {
/**
* Cancel all scheduled notifications.
*
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
private void cancelAll(CallbackContext command) {
getNotMgr().cancelAll();
@@ -341,14 +538,13 @@ private void cancelAll(CallbackContext command) {
* Clear multiple local notifications without canceling them.
*
* @param ids Set of local notification IDs.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
private void clear(JSONArray ids, CallbackContext command) {
Manager mgr = getNotMgr();
for (int i = 0; i < ids.length(); i++) {
- int id = ids.optInt(i, 0);
+ int id = ids.optInt(i, 0);
Notification toast = mgr.clear(id);
if (toast == null)
@@ -363,8 +559,7 @@ private void clear(JSONArray ids, CallbackContext command) {
/**
* Clear all triggered notifications without canceling them.
*
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
private void clearAll(CallbackContext command) {
getNotMgr().clearAll();
@@ -376,11 +571,10 @@ private void clearAll(CallbackContext command) {
* Get the type of the notification (unknown, scheduled, triggered).
*
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void type (JSONArray args, CallbackContext command) {
- int id = args.optInt(0);
+ private void type(JSONArray args, CallbackContext command) {
+ int id = args.optInt(0);
Notification toast = getNotMgr().get(id);
if (toast == null) {
@@ -389,15 +583,15 @@ private void type (JSONArray args, CallbackContext command) {
}
switch (toast.getType()) {
- case SCHEDULED:
- command.success("scheduled");
- break;
- case TRIGGERED:
- command.success("triggered");
- break;
- default:
- command.success("unknown");
- break;
+ case SCHEDULED:
+ command.success("scheduled");
+ break;
+ case TRIGGERED:
+ command.success("triggered");
+ break;
+ default:
+ command.success("unknown");
+ break;
}
}
@@ -405,27 +599,26 @@ private void type (JSONArray args, CallbackContext command) {
* Set of IDs from all existent notifications.
*
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void ids (JSONArray args, CallbackContext command) {
- int type = args.optInt(0);
+ private void ids(JSONArray args, CallbackContext command) {
+ int type = args.optInt(0);
Manager mgr = getNotMgr();
List ids;
switch (type) {
- case 0:
- ids = mgr.getIds();
- break;
- case 1:
- ids = mgr.getIdsByType(SCHEDULED);
- break;
- case 2:
- ids = mgr.getIdsByType(TRIGGERED);
- break;
- default:
- ids = new ArrayList(0);
- break;
+ case 0:
+ ids = mgr.getIds();
+ break;
+ case 1:
+ ids = mgr.getIdsByType(SCHEDULED);
+ break;
+ case 2:
+ ids = mgr.getIdsByType(TRIGGERED);
+ break;
+ default:
+ ids = new ArrayList(0);
+ break;
}
command.success(new JSONArray(ids));
@@ -435,11 +628,10 @@ private void ids (JSONArray args, CallbackContext command) {
* Options from local notification.
*
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void notification (JSONArray args, CallbackContext command) {
- int id = args.optInt(0);
+ private void notification(JSONArray args, CallbackContext command) {
+ int id = args.optInt(0);
Options opts = getNotMgr().getOptions(id);
if (opts != null) {
@@ -453,31 +645,30 @@ private void notification (JSONArray args, CallbackContext command) {
* Set of options from local notification.
*
* @param args The exec() arguments in JSON form.
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
*/
- private void notifications (JSONArray args, CallbackContext command) {
- int type = args.optInt(0);
+ private void notifications(JSONArray args, CallbackContext command) {
+ int type = args.optInt(0);
JSONArray ids = args.optJSONArray(1);
- Manager mgr = getNotMgr();
+ Manager mgr = getNotMgr();
List options;
switch (type) {
- case 0:
- options = mgr.getOptions();
- break;
- case 1:
- options = mgr.getOptionsByType(SCHEDULED);
- break;
- case 2:
- options = mgr.getOptionsByType(TRIGGERED);
- break;
- case 3:
- options = mgr.getOptionsById(toList(ids));
- break;
- default:
- options = new ArrayList(0);
- break;
+ case 0:
+ options = mgr.getOptions();
+ break;
+ case 1:
+ options = mgr.getOptionsByType(SCHEDULED);
+ break;
+ case 2:
+ options = mgr.getOptionsByType(TRIGGERED);
+ break;
+ case 3:
+ options = mgr.getOptionsById(toList(ids));
+ break;
+ default:
+ options = new ArrayList(0);
+ break;
}
command.success(new JSONArray(options));
@@ -499,8 +690,7 @@ private static synchronized void deviceready() {
/**
* Invoke success callback with a single boolean argument.
*
- * @param command The callback context used when calling back into
- * JavaScript.
+ * @param command The callback context used when calling back into JavaScript.
* @param arg The single argument to pass through.
*/
private void success(CallbackContext command, boolean arg) {
@@ -513,7 +703,7 @@ private void success(CallbackContext command, boolean arg) {
*
* @param event The event name.
*/
- private void fireEvent (String event) {
+ private void fireEvent(String event) {
fireEvent(event, null, new JSONObject());
}
@@ -523,7 +713,7 @@ private void fireEvent (String event) {
* @param event The event name.
* @param notification Optional notification to pass with.
*/
- static void fireEvent (String event, Notification notification) {
+ static void fireEvent(String event, Notification notification) {
fireEvent(event, notification, new JSONObject());
}
@@ -534,7 +724,7 @@ static void fireEvent (String event, Notification notification) {
* @param toast Optional notification to pass with.
* @param data Event object with additional data.
*/
- static void fireEvent (String event, Notification toast, JSONObject data) {
+ static void fireEvent(String event, Notification toast, JSONObject data) {
String params, js;
try {
@@ -555,8 +745,7 @@ static void fireEvent (String event, Notification toast, JSONObject data) {
params = data.toString();
}
- js = "cordova.plugins.notification.local.fireEvent(" +
- "\"" + event + "\"," + params + ")";
+ js = "cordova.plugins.notification.local.fireEvent(" + "\"" + event + "\"," + params + ")";
if (launchDetails == null && !deviceready && toast != null) {
launchDetails = new Pair(toast.getId(), event);
@@ -565,7 +754,7 @@ static void fireEvent (String event, Notification toast, JSONObject data) {
sendJavascript(js);
}
- /**
+ /**
* Use this instead of deprecated sendJavascript
*
* @param js JS code snippet as string.
@@ -577,29 +766,58 @@ private static synchronized void sendJavascript(final String js) {
return;
}
+ if (!deviceready || webView == null) {
+ eventQueue.add(js);
+ return;
+ }
+
final CordovaWebView view = webView.get();
- ((Activity)(view.getContext())).runOnUiThread(new Runnable() {
+ ((Activity) (view.getContext())).runOnUiThread(new Runnable() {
public void run() {
view.loadUrl("javascript:" + js);
+ View engineView = view.getEngine() != null ? view.getEngine().getView() : view.getView();
+
+ if (!isInForeground()) {
+ engineView.dispatchWindowVisibilityChanged(View.VISIBLE);
+ }
}
});
+
+ // Capacitor FIX: If the app is in the background, we need to make sure the notification is shown.
+ // if (webView.get() == null) {
+ // return;
+ // }
+
+ // try {
+ // final CapacitorWebView capWebView = (CapacitorWebView) webView.get().getView();
+ // if (capWebView == null) {
+ // return;
+ // }
+
+ // capWebView.post(new Runnable() {
+ // public void run() {
+ // capWebView.loadUrl("javascript:" + js);
+ // }
+ // });
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
}
/**
* If the app is running in foreground.
*/
- private static boolean isInForeground() {
+ public static boolean isInForeground() {
if (!deviceready || webView == null)
return false;
CordovaWebView view = webView.get();
- KeyguardManager km = (KeyguardManager) view.getContext()
- .getSystemService(Context.KEYGUARD_SERVICE);
+ KeyguardManager km = (KeyguardManager) view.getContext().getSystemService(Context.KEYGUARD_SERVICE);
- //noinspection SimplifiableIfStatement
+ // noinspection SimplifiableIfStatement
if (km != null && km.isKeyguardLocked())
return false;
@@ -618,7 +836,7 @@ static boolean isAppRunning() {
*
* @param ary Array of integers.
*/
- private List toList (JSONArray ary) {
+ private List toList(JSONArray ary) {
List list = new ArrayList();
for (int i = 0; i < ary.length(); i++) {
diff --git a/src/android/RestoreReceiver.java b/src/android/RestoreReceiver.java
index 0d51fd22c..82cb7add8 100644
--- a/src/android/RestoreReceiver.java
+++ b/src/android/RestoreReceiver.java
@@ -34,13 +34,16 @@
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.receiver.AbstractRestoreReceiver;
+import static de.appplant.cordova.plugin.localnotification.LocalNotification.fireEvent;
+import static de.appplant.cordova.plugin.localnotification.LocalNotification.isAppRunning;
+import static de.appplant.cordova.plugin.localnotification.LocalNotification.isInForeground;
+
/**
* This class is triggered upon reboot of the device. It needs to re-register
* the alarms with the AlarmManager since these alarms are lost in case of
* reboot.
*/
public class RestoreReceiver extends AbstractRestoreReceiver {
-
/**
* Called when a local notification need to be restored.
*
@@ -53,17 +56,29 @@ public void onRestore (Request request, Notification toast) {
boolean after = date != null && date.after(new Date());
if (!after && toast.isHighPrio()) {
- toast.show();
+ performNotification(toast);
} else {
- toast.clear();
+ // reschedule if we aren't firing here.
+ // If we do fire, performNotification takes care of
+ // next schedule.
+
+ Context ctx = toast.getContext();
+ Manager mgr = Manager.getInstance(ctx);
+
+ if (after || toast.isRepeating()) {
+ mgr.schedule(request, TriggerReceiver.class);
+ }
}
+ }
- Context ctx = toast.getContext();
- Manager mgr = Manager.getInstance(ctx);
+ @Override
+ public void dispatchAppEvent(String key, Notification notification) {
+ fireEvent(key, notification);
+ }
- if (after || toast.isRepeating()) {
- mgr.schedule(request, TriggerReceiver.class);
- }
+ @Override
+ public boolean checkAppRunning() {
+ return isAppRunning();
}
/**
diff --git a/src/android/TriggerReceiver.java b/src/android/TriggerReceiver.java
index f11a5626a..835ad38ce 100644
--- a/src/android/TriggerReceiver.java
+++ b/src/android/TriggerReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -33,14 +34,19 @@
import de.appplant.cordova.plugin.notification.Options;
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.receiver.AbstractTriggerReceiver;
+import de.appplant.cordova.plugin.notification.util.LaunchUtils;
import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.O;
import static de.appplant.cordova.plugin.localnotification.LocalNotification.fireEvent;
import static de.appplant.cordova.plugin.localnotification.LocalNotification.isAppRunning;
+import static de.appplant.cordova.plugin.localnotification.LocalNotification.isInForeground;
import static java.util.Calendar.MINUTE;
+import static android.os.Build.VERSION_CODES.P;
+
/**
* The alarm receiver is triggered when a scheduled alarm is fired. This class
* reads the information in the intent and displays this information in the
@@ -57,64 +63,18 @@ public class TriggerReceiver extends AbstractTriggerReceiver {
* @param bundle The bundled extras.
*/
@Override
- public void onTrigger (Notification notification, Bundle bundle) {
- boolean isUpdate = bundle.getBoolean(Notification.EXTRA_UPDATE, false);
- Context context = notification.getContext();
- Options options = notification.getOptions();
- Manager manager = Manager.getInstance(context);
- int badge = options.getBadgeNumber();
-
- if (badge > 0) {
- manager.setBadge(badge);
- }
-
- if (options.shallWakeUp()) {
- wakeUp(context);
- }
-
- manager.createChannel(options);
-
- notification.show();
-
- if (!isUpdate && isAppRunning()) {
- fireEvent("trigger", notification);
- }
-
- if (!options.isInfiniteTrigger())
- return;
-
- Calendar cal = Calendar.getInstance();
- cal.add(MINUTE, 1);
- Request req = new Request(options, cal.getTime());
-
- manager.schedule(req, this.getClass());
+ public void onTrigger(Notification notification, Bundle bundle) {
+ performNotification(notification);
}
- /**
- * Wakeup the device.
- *
- * @param context The application context.
- */
- private void wakeUp (Context context) {
- PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
-
- if (pm == null)
- return;
-
- int level = PowerManager.SCREEN_DIM_WAKE_LOCK
- | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-
- PowerManager.WakeLock wakeLock = pm.newWakeLock(
- level, "LocalNotification");
-
- wakeLock.setReferenceCounted(false);
- wakeLock.acquire(1000);
+ @Override
+ public void dispatchAppEvent(String key, Notification notification) {
+ fireEvent(key, notification);
+ }
- if (SDK_INT >= LOLLIPOP) {
- wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
- } else {
- wakeLock.release();
- }
+ @Override
+ public boolean checkAppRunning() {
+ return isAppRunning();
}
/**
@@ -124,12 +84,12 @@ private void wakeUp (Context context) {
* @param bundle The bundled extras.
*/
@Override
- public Notification buildNotification (Builder builder, Bundle bundle) {
- return builder
- .setClickActivity(ClickReceiver.class)
- .setClearReceiver(ClearReceiver.class)
- .setExtras(bundle)
- .build();
+ public Notification buildNotification(Builder builder, Bundle bundle) {
+ return builder
+ .setClickActivity(ClickReceiver.class)
+ .setClearReceiver(ClearReceiver.class)
+ .setExtras(bundle)
+ .build();
}
}
diff --git a/src/android/build/localnotification.gradle b/src/android/build/localnotification.gradle
index c82c7bedf..b8c20fa8c 100644
--- a/src/android/build/localnotification.gradle
+++ b/src/android/build/localnotification.gradle
@@ -24,9 +24,9 @@ repositories {
}
if (!project.ext.has('appShortcutBadgerVersion')) {
- ext.appShortcutBadgerVersion = '1.1.19'
+ ext.appShortcutBadgerVersion = '1.1.22'
}
dependencies {
- compile "me.leolin:ShortcutBadger:${appShortcutBadgerVersion}@aar"
+ implementation "me.leolin:ShortcutBadger:${appShortcutBadgerVersion}@aar"
}
diff --git a/src/android/notification/Builder.java b/src/android/notification/Builder.java
index 9e3513108..1a566067a 100644
--- a/src/android/notification/Builder.java
+++ b/src/android/notification/Builder.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -27,17 +28,22 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.MessagingStyle.Message;
-import android.support.v4.media.app.NotificationCompat.MediaStyle;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationCompat.MessagingStyle.Message;
+import androidx.media.app.NotificationCompat.MediaStyle;
import android.support.v4.media.session.MediaSessionCompat;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Paint;
+import android.graphics.Canvas;
import java.util.List;
-import java.util.Random;
import de.appplant.cordova.plugin.notification.action.Action;
+import de.appplant.cordova.plugin.notification.util.LaunchUtils;
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static de.appplant.cordova.plugin.notification.Notification.EXTRA_UPDATE;
/**
@@ -52,9 +58,6 @@ public final class Builder {
// Notification options passed by JS
private final Options options;
- // To generate unique request codes
- private final Random random = new Random();
-
// Receiver to handle the clear event
private Class> clearReceiver;
@@ -143,27 +146,41 @@ public Notification build() {
.setTimeoutAfter(options.getTimeout())
.setLights(options.getLedColor(), options.getLedOn(), options.getLedOff());
- if (sound != Uri.EMPTY && !isUpdate()) {
+ if (!sound.equals(Uri.EMPTY) && !isUpdate()) {
builder.setSound(sound);
}
+ // API < 26. Setting sound to null will prevent playing if we have no sound for any reason,
+ // including a 0 volume.
+ if (options.isWithoutSound()) {
+ builder.setSound(null);
+ }
+
if (options.isWithProgressBar()) {
builder.setProgress(
options.getProgressMaxValue(),
options.getProgressValue(),
options.isIndeterminateProgress());
- } else {
- // Only set this when no progressbar is used, to prevent a timer reset.
- builder.setWhen(options.getWhen());
}
if (options.hasLargeIcon()) {
builder.setSmallIcon(options.getSmallIcon());
- builder.setLargeIcon(options.getLargeIcon());
+
+ Bitmap largeIcon = options.getLargeIcon();
+
+ if (options.getLargeIconType().equals("circle")) {
+ largeIcon = getCircleBitmap(largeIcon);
+ }
+
+ builder.setLargeIcon(largeIcon);
} else {
builder.setSmallIcon(options.getSmallIcon());
}
+ if (options.useFullScreenIntent()) {
+ applyFullScreenIntent(builder);
+ }
+
applyStyle(builder);
applyActions(builder);
applyDeleteReceiver(builder);
@@ -172,6 +189,56 @@ public Notification build() {
return new Notification(context, options, builder);
}
+ void applyFullScreenIntent(NotificationCompat.Builder builder) {
+ String pkgName = context.getPackageName();
+
+ int notificationId = options.getId();
+ Intent intent = context
+ .getPackageManager()
+ .getLaunchIntentForPackage(pkgName)
+ .putExtra("launchNotificationId", notificationId);
+
+ PendingIntent pendingIntent =
+ LaunchUtils.getActivityPendingIntent(context, intent, notificationId);
+ builder.setFullScreenIntent(pendingIntent, true);
+ }
+
+ /**
+ * Convert a bitmap to a circular bitmap.
+ * This code has been extracted from the Phonegap Plugin Push plugin:
+ * https://github.com/phonegap/phonegap-plugin-push
+ *
+ * @param bitmap Bitmap to convert.
+ * @return Circular bitmap.
+ */
+ private Bitmap getCircleBitmap(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+
+ final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(output);
+ final int color = Color.RED;
+ final Paint paint = new Paint();
+ final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ //final RectF rectF = new RectF(rect);
+
+ paint.setAntiAlias(true);
+ canvas.drawARGB(0, 0, 0, 0);
+ paint.setColor(color);
+ float cx = bitmap.getWidth() / 2.0f;
+ float cy = bitmap.getHeight() / 2.0f;
+ float radius = Math.min(cx, cy);
+ canvas.drawCircle(cx, cy, radius, paint);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(bitmap, rect, rect, paint);
+
+ bitmap.recycle();
+
+ return output;
+ }
+
/**
* Find out and set the notification style.
*
@@ -316,19 +383,17 @@ private void applyDeleteReceiver(NotificationCompat.Builder builder) {
if (clearReceiver == null)
return;
+ int notificationId = options.getId();
Intent intent = new Intent(context, clearReceiver)
.setAction(options.getIdentifier())
- .putExtra(Notification.EXTRA_ID, options.getId());
+ .putExtra(Notification.EXTRA_ID, notificationId);
if (extras != null) {
intent.putExtras(extras);
}
- int reqCode = random.nextInt();
-
- PendingIntent deleteIntent = PendingIntent.getBroadcast(
- context, reqCode, intent, FLAG_UPDATE_CURRENT);
-
+ PendingIntent deleteIntent =
+ LaunchUtils.getBroadcastPendingIntent(context, intent, notificationId);
builder.setDeleteIntent(deleteIntent);
}
@@ -343,8 +408,16 @@ private void applyContentReceiver(NotificationCompat.Builder builder) {
if (clickActivity == null)
return;
+ Action[] actions = options.getActions();
+ if (actions != null && actions.length > 0 ) {
+ // if actions are defined, the user must click on button actions to launch the app.
+ // Don't make the notification clickable in this case
+ return;
+ }
+
+ int notificationId = options.getId();
Intent intent = new Intent(context, clickActivity)
- .putExtra(Notification.EXTRA_ID, options.getId())
+ .putExtra(Notification.EXTRA_ID, notificationId)
.putExtra(Action.EXTRA_ID, Action.CLICK_ACTION_ID)
.putExtra(Options.EXTRA_LAUNCH, options.isLaunchingApp())
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
@@ -353,11 +426,8 @@ private void applyContentReceiver(NotificationCompat.Builder builder) {
intent.putExtras(extras);
}
- int reqCode = random.nextInt();
-
- PendingIntent contentIntent = PendingIntent.getService(
- context, reqCode, intent, FLAG_UPDATE_CURRENT);
-
+ PendingIntent contentIntent =
+ LaunchUtils.getTaskStackPendingIntent(context, intent, notificationId);
builder.setContentIntent(contentIntent);
}
@@ -393,8 +463,9 @@ private void applyActions (NotificationCompat.Builder builder) {
* @param action Notification action needing the PendingIntent
*/
private PendingIntent getPendingIntentForAction (Action action) {
+ int notificationId = options.getId();
Intent intent = new Intent(context, clickActivity)
- .putExtra(Notification.EXTRA_ID, options.getId())
+ .putExtra(Notification.EXTRA_ID, notificationId)
.putExtra(Action.EXTRA_ID, action.getId())
.putExtra(Options.EXTRA_LAUNCH, action.isLaunchingApp())
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
@@ -403,10 +474,7 @@ private PendingIntent getPendingIntentForAction (Action action) {
intent.putExtras(extras);
}
- int reqCode = random.nextInt();
-
- return PendingIntent.getService(
- context, reqCode, intent, FLAG_UPDATE_CURRENT);
+ return LaunchUtils.getTaskStackPendingIntent(context, intent, notificationId);
}
/**
@@ -415,7 +483,8 @@ private PendingIntent getPendingIntentForAction (Action action) {
* @return true in case of an updated version.
*/
private boolean isUpdate() {
- return extras != null && extras.getBoolean(EXTRA_UPDATE, false);
+ return extras != null
+ && extras.getBoolean(EXTRA_UPDATE, false);
}
/**
diff --git a/src/android/notification/Manager.java b/src/android/notification/Manager.java
index 0e1e2fdbc..08ef7c1e1 100644
--- a/src/android/notification/Manager.java
+++ b/src/android/notification/Manager.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -23,15 +24,14 @@
package de.appplant.cordova.plugin.notification;
-import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioAttributes;
-import android.media.RingtoneManager;
+import android.net.Uri;
import android.service.notification.StatusBarNotification;
-import android.support.v4.app.NotificationManagerCompat;
+import androidx.core.app.NotificationManagerCompat;
import org.json.JSONException;
import org.json.JSONObject;
@@ -45,30 +45,18 @@
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
-import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
-import static android.support.v4.app.NotificationCompat.PRIORITY_LOW;
-import static android.support.v4.app.NotificationCompat.PRIORITY_DEFAULT;
-import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH;
-import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_MIN;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_LOW;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_HIGH;
+import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
+import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH;
+import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW;
import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY_ID;
import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED;
-import de.appplant.cordova.plugin.notification.Options;
/**
- * Central way to access all or single local notifications set by specific
- * state like triggered or scheduled. Offers shortcut ways to schedule,
- * cancel or clear local notifications.
+ * Central way to access all or single local notifications set by specific state
+ * like triggered or scheduled. Offers shortcut ways to schedule, cancel or
+ * clear local notifications.
*/
public final class Manager {
-
- static final String DEFAULT_CHANNEL_ID = "default-channel-id";
-
- static final String DEFAULT_CHANNEL_DESCRIPTION = "Default channel";
-
// The application context
private Context context;
@@ -79,7 +67,6 @@ public final class Manager {
*/
private Manager(Context context) {
this.context = context;
- //createDefaultChannel();
}
/**
@@ -94,18 +81,18 @@ public static Manager getInstance(Context context) {
/**
* Check if app has local notification permission.
*/
- public boolean hasPermission () {
+ public boolean hasPermission() {
return getNotCompMgr().areNotificationsEnabled();
}
/**
* Schedule local notification specified by request.
*
- * @param request Set of notification options.
+ * @param request Set of notification options.
* @param receiver Receiver to handle the trigger event.
*/
- public Notification schedule (Request request, Class> receiver) {
- Options options = request.getOptions();
+ public Notification schedule(Request request, Class> receiver) {
+ Options options = request.getOptions();
Notification toast = new Notification(context, options);
toast.schedule(request, receiver);
@@ -114,63 +101,76 @@ public Notification schedule (Request request, Class> receiver) {
}
/**
- * TODO: temporary
+ * Build channel with options
+ *
+ * @param soundUri Uri for custom sound (empty to use default)
+ * @param shouldVibrate whether not vibration should occur during the
+ * notification
+ * @param hasSound whether or not sound should play during the notification
+ * @param channelName the name of the channel (null will pick an appropriate
+ * default name for the options provided).
+ * @return channel ID of newly created (or reused) channel
+ */
+ public String buildChannelWithOptions(Uri soundUri, boolean shouldVibrate, boolean hasSound,
+ CharSequence channelName, String channelId) {
+ String defaultChannelId, newChannelId;
+ CharSequence defaultChannelName;
+ int importance;
+
+ if (hasSound && shouldVibrate) {
+ defaultChannelId = Options.SOUND_VIBRATE_CHANNEL_ID;
+ defaultChannelName = Options.SOUND_VIBRATE_CHANNEL_NAME;
+ importance = IMPORTANCE_HIGH;
+ shouldVibrate = true;
+ } else if (hasSound) {
+ defaultChannelId = Options.SOUND_CHANNEL_ID;
+ defaultChannelName = Options.SOUND_CHANNEL_NAME;
+ importance = IMPORTANCE_DEFAULT;
+ shouldVibrate = false;
+ } else if (shouldVibrate) {
+ defaultChannelId = Options.VIBRATE_CHANNEL_ID;
+ defaultChannelName = Options.VIBRATE_CHANNEL_NAME;
+ importance = IMPORTANCE_LOW;
+ shouldVibrate = true;
+ } else {
+ defaultChannelId = Options.DEFAULT_CHANNEL_ID;
+ defaultChannelName = "Default Channel";
+ importance = IMPORTANCE_HIGH;
+ shouldVibrate = true;
+ }
+
+ newChannelId = channelId != null ? channelId : defaultChannelId;
+
+ createChannel(newChannelId, channelName != null ? channelName : defaultChannelName, importance, shouldVibrate,
+ soundUri);
+
+ return newChannelId;
+ }
+
+ /**
+ * Create a channel
*/
- @SuppressLint("WrongConstant")
- public void createChannel(Options options) {
+ public void createChannel(String channelId, CharSequence channelName, int importance, Boolean shouldVibrate,
+ Uri soundUri) {
NotificationManager mgr = getNotMgr();
- int importance = IMPORTANCE_DEFAULT;
if (SDK_INT < O)
return;
- NotificationChannel channel = mgr.getNotificationChannel(options.getChannel());
+ NotificationChannel channel = mgr.getNotificationChannel(channelId);
if (channel != null)
return;
- switch (options.getPrio()) {
- case PRIORITY_MIN:
- importance = IMPORTANCE_MIN;
- break;
- case PRIORITY_LOW:
- importance = IMPORTANCE_LOW;
- break;
- case PRIORITY_DEFAULT:
- importance = IMPORTANCE_DEFAULT;
- break;
- case PRIORITY_HIGH:
- importance = IMPORTANCE_HIGH;
- break;
- case PRIORITY_MAX:
- importance = IMPORTANCE_HIGH;
- break;
- }
+ channel = new NotificationChannel(channelId, channelName, importance);
- channel = new NotificationChannel(
- options.getChannel(), options.getChannelDescription(), importance);
- if(!options.isSilent() && importance > IMPORTANCE_DEFAULT) channel.setBypassDnd(true);
- if(!options.isWithoutLights()) channel.enableLights(true);
- if(options.isWithVibration()) {
- channel.enableVibration(true);
- } else {
- channel.setVibrationPattern(new long[]{ 0 });
- channel.enableVibration(true);
- }
- channel.setLightColor(options.getLedColor());
- if(options.isWithoutSound()) {
- channel.setSound(null, null);
- } else {
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
-
- if(options.isWithDefaultSound()) {
- channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), audioAttributes);
- } else {
- channel.setSound(options.getSound(), audioAttributes);
- }
- }
+ channel.enableVibration(shouldVibrate);
+
+ if (!soundUri.equals(Uri.EMPTY)) {
+ AudioAttributes attributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build();
+ channel.setSound(soundUri, attributes);
+ }
mgr.createNotificationChannel(channel);
}
@@ -182,7 +182,7 @@ public void createChannel(Options options) {
* @param updates JSON object with notification options.
* @param receiver Receiver to handle the trigger event.
*/
- public Notification update (int id, JSONObject updates, Class> receiver) {
+ public Notification update(int id, JSONObject updates, Class> receiver) {
Notification notification = get(id);
if (notification == null)
@@ -198,7 +198,7 @@ public Notification update (int id, JSONObject updates, Class> receiver) {
*
* @param id The notification ID.
*/
- public Notification clear (int id) {
+ public Notification clear(int id) {
Notification toast = get(id);
if (toast != null) {
@@ -211,7 +211,7 @@ public Notification clear (int id) {
/**
* Clear all local notifications.
*/
- public void clearAll () {
+ public void clearAll() {
List toasts = getByType(TRIGGERED);
for (Notification toast : toasts) {
@@ -227,7 +227,7 @@ public void clearAll () {
*
* @param id The notification ID
*/
- public Notification cancel (int id) {
+ public Notification cancel(int id) {
Notification toast = get(id);
if (toast != null) {
@@ -240,7 +240,7 @@ public Notification cancel (int id) {
/**
* Cancel all local notifications.
*/
- public void cancelAll () {
+ public void cancelAll() {
List notifications = getAll();
for (Notification notification : notifications) {
@@ -280,7 +280,7 @@ public List getIdsByType(Notification.Type type) {
return getIds();
StatusBarNotification[] activeToasts = getActiveNotifications();
- List activeIds = new ArrayList();
+ List activeIds = new ArrayList();
for (StatusBarNotification toast : activeToasts) {
activeIds.add(toast.getId());
@@ -365,8 +365,7 @@ public List getOptionsById(List ids) {
/**
* List of properties from all local notifications from given type.
*
- * @param type
- * The notification life cycle type
+ * @param type The notification life cycle type
*/
public List getOptionsByType(Notification.Type type) {
ArrayList options = new ArrayList();
@@ -388,13 +387,13 @@ public List getOptionsByType(Notification.Type type) {
*/
public Options getOptions(int id) {
SharedPreferences prefs = getPrefs();
- String toastId = Integer.toString(id);
+ String toastId = Integer.toString(id);
if (!prefs.contains(toastId))
return null;
try {
- String json = prefs.getString(toastId, null);
+ String json = prefs.getString(toastId, null);
JSONObject dict = new JSONObject(json);
return new Options(context, dict);
@@ -425,7 +424,7 @@ public Notification get(int id) {
*
* @param badge The badge number.
*/
- public void setBadge (int badge) {
+ public void setBadge(int badge) {
if (badge == 0) {
new BadgeImpl(context).clearBadge();
} else {
@@ -447,7 +446,7 @@ StatusBarNotification[] getActiveNotifications() {
/**
* Shared private preferences for the application.
*/
- private SharedPreferences getPrefs () {
+ private SharedPreferences getPrefs() {
return context.getSharedPreferences(PREF_KEY_ID, Context.MODE_PRIVATE);
}
@@ -455,16 +454,16 @@ private SharedPreferences getPrefs () {
* Notification manager for the application.
*/
private NotificationManager getNotMgr() {
- return (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
+ return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* Notification compat manager for the application.
*/
- public NotificationManagerCompat getNotCompMgr() {
+ private NotificationManagerCompat getNotCompMgr() {
return NotificationManagerCompat.from(context);
}
+
}
// codebeat:enable[TOO_MANY_FUNCTIONS]
diff --git a/src/android/notification/Notification.java b/src/android/notification/Notification.java
index c3bc3e673..1fd7ce6ba 100644
--- a/src/android/notification/Notification.java
+++ b/src/android/notification/Notification.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -30,9 +31,7 @@
import android.content.SharedPreferences;
import android.net.Uri;
import android.service.notification.StatusBarNotification;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.util.ArraySet;
-import android.support.v4.util.Pair;
+import android.util.Pair;
import android.util.Log;
import android.util.SparseArray;
@@ -44,17 +43,18 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import androidx.collection.ArraySet;
+import androidx.core.app.NotificationCompat;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
-import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.M;
-import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_MIN;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_LOW;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_MAX;
-import static android.support.v4.app.NotificationManagerCompat.IMPORTANCE_HIGH;
+import static androidx.core.app.NotificationCompat.PRIORITY_HIGH;
+import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
+import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
+
+import de.appplant.cordova.plugin.notification.util.LaunchUtils;
/**
* Wrapper class around OS notification class. Handles basic operations
@@ -218,16 +218,16 @@ void schedule(Request request, Class> receiver) {
if (!date.after(new Date()) && trigger(intent, receiver))
continue;
-
- PendingIntent pi = PendingIntent.getBroadcast(
- context, 0, intent, FLAG_CANCEL_CURRENT);
+ int notificationId = options.getId();
+ PendingIntent pi =
+ LaunchUtils.getBroadcastPendingIntent(context, intent, notificationId);
try {
switch (options.getPrio()) {
- case IMPORTANCE_MIN: case IMPORTANCE_LOW:
+ case PRIORITY_MIN:
mgr.setExact(RTC, time, pi);
break;
- case IMPORTANCE_MAX: case IMPORTANCE_HIGH:
+ case PRIORITY_MAX: case PRIORITY_HIGH:
if (SDK_INT >= M) {
mgr.setExactAndAllowWhileIdle(RTC_WAKEUP, time, pi);
} else {
@@ -297,7 +297,8 @@ public void cancel() {
*/
private void cancelScheduledAlarms() {
SharedPreferences prefs = getPrefs(PREF_KEY_PID);
- String id = options.getIdentifier();
+ String id = options.getIdentifier();
+ int notificationId = options.getId();
Set actions = prefs.getStringSet(id, null);
if (actions == null)
@@ -305,10 +306,7 @@ private void cancelScheduledAlarms() {
for (String action : actions) {
Intent intent = new Intent(action);
-
- PendingIntent pi = PendingIntent.getBroadcast(
- context, 0, intent, 0);
-
+ PendingIntent pi = LaunchUtils.getBroadcastPendingIntent(context, intent, notificationId);
if (pi != null) {
getAlarmMgr().cancel(pi);
}
@@ -326,6 +324,8 @@ public void show() {
}
grantPermissionToPlaySoundFromExternal();
+ new NotificationVolumeManager(context, options)
+ .adjustAlarmVolume();
getNotMgr().notify(getId(), builder.build());
}
diff --git a/src/android/notification/NotificationVolumeManager.java b/src/android/notification/NotificationVolumeManager.java
new file mode 100644
index 000000000..7fb77c33d
--- /dev/null
+++ b/src/android/notification/NotificationVolumeManager.java
@@ -0,0 +1,233 @@
+package de.appplant.cordova.plugin.notification;
+
+import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.M;
+import static java.lang.Thread.sleep;
+
+/**
+ * Class to handle all notification volume changes
+ */
+public class NotificationVolumeManager {
+ /**
+ * Amount of time to sleep while polling to see if all volume writers are closed.
+ */
+ final private int VOLUME_WRITER_POLLING_DURATION = 200;
+
+ /**
+ * Key for volume writer counter in shared preferences
+ */
+ final private String VOLUME_CONFIG_WRITER_COUNT_KEY = "volumeConfigWriterCount";
+
+ /**
+ * Tag for logs
+ */
+ final String TAG = "NotificationVolumeMgr";
+
+ /**
+ * Notification manager
+ */
+ private NotificationManager notificationManager;
+
+ /**
+ * Audio Manager
+ */
+ private AudioManager audioManager;
+
+ /**
+ * Shared preferences, used to store settings across processes
+ */
+ private SharedPreferences settings;
+
+ /**
+ * Options for the notification
+ */
+ private Options options;
+
+ /**
+ * Initialize the NotificationVolumeManager
+ * @param context Application context
+ */
+ public NotificationVolumeManager (Context context, Options options) {
+ this.settings = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
+ this.notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
+ this.audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+ this.options = options;
+ }
+
+ /**
+ * Ensure that this is the only volume writer.
+ * Wait until others have closed.
+ * TODO: Better locking mechanism to ensure concurrency (file lock?)
+ * @throws InterruptedException Throws an interrupted exception, required by sleep call.
+ */
+ @SuppressLint("ApplySharedPref")
+ private void ensureOnlyVolumeWriter () throws InterruptedException {
+ int writerCount = settings.getInt(VOLUME_CONFIG_WRITER_COUNT_KEY, 0) + 1;
+ settings.edit().putInt(VOLUME_CONFIG_WRITER_COUNT_KEY, writerCount).commit();
+
+ int resetDelay = options.getResetDelay();
+ if (resetDelay == 0) {
+ resetDelay = Options.DEFAULT_RESET_DELAY;
+ }
+
+ int resetDelayMs = resetDelay * 1000;
+ int sleepTotal = 0;
+
+ // Wait until we are the only writer left.
+ while(writerCount > 1) {
+ if (sleepTotal > resetDelayMs) {
+ throw new InterruptedException("Volume writer timeout exceeded reset delay." +
+ "Something must have gone wrong. Reset volume writer counts to 0 " +
+ "and reset volume settings to user settings.");
+ }
+
+ sleep(VOLUME_WRITER_POLLING_DURATION);
+ sleepTotal += VOLUME_WRITER_POLLING_DURATION;
+
+ writerCount = settings.getInt(VOLUME_CONFIG_WRITER_COUNT_KEY, 0);
+ }
+ }
+
+ /**
+ * Remove one count from active volume writers. Used when writer is finished.
+ */
+ @SuppressLint("ApplySharedPref")
+ private void decrementVolumeWriter () {
+ int writerCount = settings.getInt(VOLUME_CONFIG_WRITER_COUNT_KEY, 0) - 1;
+ settings.edit().putInt(VOLUME_CONFIG_WRITER_COUNT_KEY, Math.max(writerCount, 0)).commit();
+ }
+
+ /**
+ * Reset volume writer counts to 0. To be used in error conditions.
+ */
+ @SuppressLint("ApplySharedPref")
+ private void resetVolumeWriter () {
+ settings.edit().putInt(VOLUME_CONFIG_WRITER_COUNT_KEY, 0).commit();
+ }
+
+ /**
+ * Set the volume for our ringer
+ * @param ringerMode ringer mode enum. Normal ringer or vibration.
+ * @param volume volume.
+ */
+ private void setVolume (int ringerMode, int volume) {
+ // After delay, user could have set phone to do not disturb.
+ // If so and we can't change the ringer, quit so we don't create an error condition
+ if (canChangeRinger()) {
+ // Change ringer mode
+ audioManager.setRingerMode(ringerMode);
+
+ // Change to new Volume
+ audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, volume, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ }
+ }
+
+ /**
+ * Set the volume to the last user settings from shared preferences.
+ */
+ private void setVolumeToUserSettings () {
+ int ringMode = settings.getInt("userRingerMode", -1);
+ int volume = settings.getInt("userVolume", -1);
+
+ setVolume(ringMode, volume);
+ }
+
+ /**
+ * Figure out if we can change the ringer.
+ * In Android M+, we can't change out of do not disturb if we don't have explicit permission.
+ * @return whether or not we can change the ringer.
+ */
+ private boolean canChangeRinger() {
+ return SDK_INT < M || notificationManager.isNotificationPolicyAccessGranted()
+ || audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT;
+ }
+
+ /**
+ * Adjusts alarm Volume
+ * Options object. Contains our volume, reset and vibration settings.
+ */
+ @SuppressLint("ApplySharedPref")
+ public void adjustAlarmVolume () {
+ Integer volume = options.getVolume();
+
+ if (volume.equals(Options.VOLUME_NOT_SET) || !canChangeRinger()) {
+ return;
+ }
+
+ try {
+ ensureOnlyVolumeWriter();
+
+ boolean vibrate = options.isWithVibration();
+
+ int delay = options.getResetDelay();
+
+ if (delay <= 0) {
+ delay = Options.DEFAULT_RESET_DELAY;
+ }
+
+ // Count of all alarms currently sounding
+ Integer count = settings.getInt("alarmCount", 0);
+ settings.edit().putInt("alarmCount", count + 1).commit();
+
+ // Get current phone volume
+ int userVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION);
+
+ // Get Ringer mode
+ int userRingerMode = audioManager.getRingerMode();
+
+ // If this is the first alarm store the users ringer and volume settings
+ if (count.equals(0)) {
+ settings.edit().putInt("userVolume", userVolume).apply();
+ settings.edit().putInt("userRingerMode", userRingerMode).apply();
+ }
+
+ // Calculates a new volume based on the study configure volume percentage and the devices max volume integer
+ if (volume > 0) {
+ // Gets devices max volume integer
+ int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
+
+ // Calculates new volume based on devices max volume
+ double newVolume = Math.ceil(maxVolume * (volume / 100.00));
+
+ setVolume(AudioManager.RINGER_MODE_NORMAL, (int) newVolume);
+ } else {
+ // Volume of 0
+ if (vibrate) {
+ // Change mode to vibrate
+ setVolume(AudioManager.RINGER_MODE_VIBRATE, 0);
+ }
+ }
+
+ // Timer to change users sound back
+ Timer timer = new Timer();
+ timer.schedule(new TimerTask() {
+ public void run() {
+ int currentCount = settings.getInt("alarmCount", 0);
+ currentCount = Math.max(currentCount - 1, 0);
+ settings.edit().putInt("alarmCount", currentCount).apply();
+
+ if (currentCount == 0) {
+ setVolumeToUserSettings();
+ }
+ }
+ }, delay * 1000);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupted waiting for volume set. "
+ + "Reset to user setting, and set counts to 0: " + e.toString());
+ resetVolumeWriter();
+ setVolumeToUserSettings();
+ } finally {
+ decrementVolumeWriter();
+ }
+ }
+}
diff --git a/src/android/notification/Options.java b/src/android/notification/Options.java
index efefcb4be..ecb206ddb 100644
--- a/src/android/notification/Options.java
+++ b/src/android/notification/Options.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -27,14 +28,13 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.MessagingStyle.Message;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationCompat.MessagingStyle.Message;
import android.support.v4.media.session.MediaSessionCompat;
import org.json.JSONArray;
import org.json.JSONObject;
-import java.lang.System;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
@@ -44,20 +44,40 @@
import de.appplant.cordova.plugin.notification.action.ActionGroup;
import de.appplant.cordova.plugin.notification.util.AssetUtil;
-import static android.support.v4.app.NotificationCompat.DEFAULT_LIGHTS;
-import static android.support.v4.app.NotificationCompat.DEFAULT_SOUND;
-import static android.support.v4.app.NotificationCompat.DEFAULT_VIBRATE;
-import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
-import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
-import static android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC;
-import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.O;
+import static androidx.core.app.NotificationCompat.DEFAULT_LIGHTS;
+import static androidx.core.app.NotificationCompat.DEFAULT_SOUND;
+import static androidx.core.app.NotificationCompat.DEFAULT_VIBRATE;
+import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
+import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
+import static androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC;
+import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
/**
- * Wrapper around the JSON object passed through JS which contains all
- * possible option values. Class provides simple readers and more advanced
- * methods to convert independent values into platform specific values.
+ * Wrapper around the JSON object passed through JS which contains all possible
+ * option values. Class provides simple readers and more advanced methods to
+ * convert independent values into platform specific values.
*/
public final class Options {
+ // Default Channel ID for SDK < 26
+ static final String DEFAULT_CHANNEL_ID = "default-channel-id";
+
+ // Silent channel
+ static final String SILENT_CHANNEL_ID = "silent-channel-id";
+ static final CharSequence SILENT_CHANNEL_NAME = "Silent Notifications";
+
+ // Vibrate only channel
+ static final String VIBRATE_CHANNEL_ID = "vibrate-channel-id";
+ static final CharSequence VIBRATE_CHANNEL_NAME = "Low Priority Notifications";
+
+ // Sound only channel
+ static final String SOUND_CHANNEL_ID = "sound-channel-id";
+ static final CharSequence SOUND_CHANNEL_NAME = "Medium Priority Notifications";
+
+ // Sound and vibrate channel
+ static final String SOUND_VIBRATE_CHANNEL_ID = "sound-vibrate-channel-id";
+ static final CharSequence SOUND_VIBRATE_CHANNEL_NAME = "High Priority Notifications";
// Key name for bundled sound extra
static final String EXTRA_SOUND = "NOTIFICATION_SOUND";
@@ -68,6 +88,16 @@ public final class Options {
// Default icon path
private static final String DEFAULT_ICON = "res://icon";
+ public final static Integer DEFAULT_RESET_DELAY = 5;
+
+ public final static Integer VOLUME_NOT_SET = -1;
+
+ // Default wakelock timeout
+ public final static Integer DEFAULT_WAKE_LOCK_TIMEOUT = 15000;
+
+ // Default icon type
+ private static final String DEFAULT_ICON_TYPE = "square";
+
// The original JSON object
private final JSONObject options;
@@ -85,7 +115,7 @@ public final class Options {
public Options(JSONObject options) {
this.options = options;
this.context = null;
- this.assets = null;
+ this.assets = null;
}
/**
@@ -97,13 +127,13 @@ public Options(JSONObject options) {
public Options(Context context, JSONObject options) {
this.context = context;
this.options = options;
- this.assets = AssetUtil.getInstance(context);
+ this.assets = AssetUtil.getInstance(context);
}
/**
* Application context.
*/
- public Context getContext () {
+ public Context getContext() {
return context;
}
@@ -195,6 +225,13 @@ boolean isLaunchingApp() {
return options.optBoolean("launch", true);
}
+ /**
+ * flag to auto-launch the application as the notification fires
+ */
+ public boolean isAutoLaunchingApp() {
+ return options.optBoolean("autoLaunch", true);
+ }
+
/**
* wakeup flag for the notification.
*/
@@ -202,6 +239,23 @@ public boolean shallWakeUp() {
return options.optBoolean("wakeup", true);
}
+ /**
+ * Use a fullScreenIntent
+ */
+ public boolean useFullScreenIntent() { return options.optBoolean("fullScreenIntent", true); }
+
+ /**
+ * Whether or not to trigger a notification in the app.
+ */
+ public boolean triggerInApp() { return options.optBoolean("triggerInApp", false); }
+
+ /**
+ * Timeout for wakeup (only used if shallWakeUp() is true)
+ */
+ public int getWakeLockTimeout() {
+ return options.optInt("wakeLockTimeout", DEFAULT_WAKE_LOCK_TIMEOUT);
+ }
+
/**
* Gets the value for the timeout flag.
*/
@@ -212,8 +266,24 @@ long getTimeout() {
/**
* The channel id of that notification.
*/
- String getChannel() { return options.optString("channel", Manager.DEFAULT_CHANNEL_ID); }
- String getChannelDescription() { return options.optString("channelDescription", Manager.DEFAULT_CHANNEL_DESCRIPTION); }
+ String getChannel() {
+ // If we have a low enough SDK for it not to matter,
+ // short-circuit.
+ if (SDK_INT < O) {
+ return DEFAULT_CHANNEL_ID;
+ }
+
+ Uri soundUri = getSound();
+ boolean hasSound = !isWithoutSound();
+ boolean shouldVibrate = isWithVibration();
+ CharSequence channelName = options.optString("channelName", null);
+ String channelId = options.optString("channelId", null);
+
+ channelId = Manager.getInstance(context).buildChannelWithOptions(soundUri, shouldVibrate, hasSound, channelName,
+ channelId);
+
+ return channelId;
+ }
/**
* If the group shall show a summary.
@@ -237,8 +307,7 @@ public String getTitle() {
String title = options.optString("title", "");
if (title.isEmpty()) {
- title = context.getApplicationInfo().loadLabel(
- context.getPackageManager()).toString();
+ title = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString();
}
return title;
@@ -253,11 +322,9 @@ int getLedColor() {
if (cfg instanceof String) {
hex = options.optString("led");
- } else
- if (cfg instanceof JSONArray) {
+ } else if (cfg instanceof JSONArray) {
hex = options.optJSONArray("led").optString(0);
- } else
- if (cfg instanceof JSONObject) {
+ } else if (cfg instanceof JSONObject) {
hex = options.optJSONObject("led").optString("color");
}
@@ -265,7 +332,7 @@ int getLedColor() {
return 0;
try {
- hex = stripHex(hex);
+ hex = stripHex(hex);
int aRGB = Integer.parseInt(hex, 16);
return aRGB + 0xFF000000;
@@ -323,9 +390,7 @@ public int getColor() {
hex = stripHex(hex);
if (hex.matches("[^0-9]*")) {
- return Color.class
- .getDeclaredField(hex.toUpperCase())
- .getInt(null);
+ return Color.class.getDeclaredField(hex.toUpperCase()).getInt(null);
}
int aRGB = Integer.parseInt(hex, 16);
@@ -361,33 +426,36 @@ boolean hasLargeIcon() {
*/
Bitmap getLargeIcon() {
String icon = options.optString("icon", null);
- Uri uri = assets.parse(icon);
- Bitmap bmp = null;
+ Uri uri = assets.parse(icon);
+ Bitmap bmp = null;
try {
bmp = assets.getIconFromUri(uri);
- } catch (Exception e){
+ } catch (Exception e) {
e.printStackTrace();
}
return bmp;
}
+ /**
+ * Type of the large icon.
+ */
+ String getLargeIconType() {
+ return options.optString("iconType", DEFAULT_ICON_TYPE);
+ }
+
/**
* Small icon resource ID for the local notification.
*/
int getSmallIcon() {
String icon = options.optString("smallIcon", DEFAULT_ICON);
- int resId = assets.getResId(icon);
+ int resId = assets.getResId(icon);
if (resId == 0) {
resId = assets.getResId(DEFAULT_ICON);
}
- if (resId == 0) {
- resId = context.getApplicationInfo().icon;
- }
-
if (resId == 0) {
resId = android.R.drawable.ic_popup_reminder;
}
@@ -395,6 +463,23 @@ int getSmallIcon() {
return resId;
}
+ /**
+ * Get the volume
+ */
+ public Integer getVolume() {
+ return options.optInt("alarmVolume", VOLUME_NOT_SET);
+ }
+
+ /**
+ * Returns the resetDelay until the sound changes revert back to the users
+ * settings.
+ *
+ * @return resetDelay
+ */
+ public Integer getResetDelay() {
+ return options.optInt("resetDelay", DEFAULT_RESET_DELAY);
+ }
+
/**
* If the phone should vibrate.
*/
@@ -407,7 +492,7 @@ public boolean isWithVibration() {
*/
public boolean isWithoutSound() {
Object value = options.opt("sound");
- return value == null || value.equals(false);
+ return value == null || value.equals(false) || options.optInt("alarmVolume") == 0;
}
/**
@@ -421,7 +506,7 @@ public boolean isWithDefaultSound() {
/**
* If the phone should show no LED light.
*/
- public boolean isWithoutLights() {
+ private boolean isWithoutLights() {
Object value = options.opt("led");
return value == null || value.equals(false);
}
@@ -429,15 +514,15 @@ public boolean isWithoutLights() {
/**
* If the phone should show the default LED lights.
*/
- public boolean isWithDefaultLights() {
+ private boolean isWithDefaultLights() {
Object value = options.opt("led");
return value != null && value.equals(true);
}
/**
- * Set the default notification options that will be used.
- * The value should be one or more of the following fields combined with
- * bitwise-or: DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
+ * Set the default notification options that will be used. The value should be
+ * one or more of the following fields combined with bitwise-or: DEFAULT_SOUND,
+ * DEFAULT_VIBRATE, DEFAULT_LIGHTS.
*/
int getDefaults() {
int defaults = options.optInt("defaults", 0);
@@ -450,15 +535,13 @@ int getDefaults() {
if (isWithDefaultSound()) {
defaults |= DEFAULT_SOUND;
- } else
- if (isWithoutSound()) {
+ } else if (isWithoutSound()) {
defaults &= DEFAULT_SOUND;
}
if (isWithDefaultLights()) {
defaults |= DEFAULT_LIGHTS;
- } else
- if (isWithoutLights()) {
+ } else if (isWithoutLights()) {
defaults &= DEFAULT_LIGHTS;
}
@@ -482,18 +565,11 @@ int getVisibility() {
* Gets the notifications priority.
*/
int getPrio() {
- return Math.min(Math.max(options.optInt("priority"), PRIORITY_MIN), PRIORITY_MAX);
- }
-
- /**
- * Set the when date for the notification.
- */
- long getWhen() {
- long when = options.optLong("when");
+ int prio = options.optInt("priority");
- return (when != 0) ? when : System.currentTimeMillis();
+ return Math.min(Math.max(prio, PRIORITY_MIN), PRIORITY_MAX);
}
-
+
/**
* If the notification shall show the when date.
*/
@@ -516,9 +592,7 @@ boolean showChronometer() {
* If the notification shall display a progress bar.
*/
boolean isWithProgressBar() {
- return options
- .optJSONObject("progressBar")
- .optBoolean("enabled", false);
+ return options.optJSONObject("progressBar").optBoolean("enabled", false);
}
/**
@@ -527,9 +601,7 @@ boolean isWithProgressBar() {
* @return 0 by default.
*/
int getProgressValue() {
- return options
- .optJSONObject("progressBar")
- .optInt("value", 0);
+ return options.optJSONObject("progressBar").optInt("value", 0);
}
/**
@@ -538,9 +610,7 @@ int getProgressValue() {
* @return 100 by default.
*/
int getProgressMaxValue() {
- return options
- .optJSONObject("progressBar")
- .optInt("maxValue", 100);
+ return options.optJSONObject("progressBar").optInt("maxValue", 100);
}
/**
@@ -549,9 +619,7 @@ int getProgressMaxValue() {
* @return false by default.
*/
boolean isIndeterminateProgress() {
- return options
- .optJSONObject("progressBar")
- .optBoolean("indeterminate", false);
+ return options.optJSONObject("progressBar").optBoolean("indeterminate", false);
}
/**
@@ -573,11 +641,11 @@ String getSummary() {
/**
* Image attachments for image style notifications.
*
- * @return For now it only returns the first item as Android does not
- * support multiple attachments like iOS.
+ * @return For now it only returns the first item as Android does not support
+ * multiple attachments like iOS.
*/
List getAttachments() {
- JSONArray paths = options.optJSONArray("attachments");
+ JSONArray paths = options.optJSONArray("attachments");
List pics = new ArrayList();
if (paths == null)
@@ -605,22 +673,20 @@ List getAttachments() {
* Gets the list of actions to display.
*/
Action[] getActions() {
- Object value = options.opt("actions");
- String groupId = null;
+ Object value = options.opt("actions");
+ String groupId = null;
JSONArray actions = null;
ActionGroup group = null;
if (value instanceof String) {
groupId = (String) value;
- } else
- if (value instanceof JSONArray) {
+ } else if (value instanceof JSONArray) {
actions = (JSONArray) value;
}
if (groupId != null) {
group = ActionGroup.lookup(groupId);
- } else
- if (actions != null && actions.length() > 0) {
+ } else if (actions != null && actions.length() > 0) {
group = ActionGroup.parse(context, actions);
}
@@ -644,13 +710,13 @@ Message[] getMessages() {
return null;
Message[] messages = new Message[list.length()];
- long now = new Date().getTime();
+ long now = new Date().getTime();
for (int i = 0; i < messages.length; i++) {
JSONObject msg = list.optJSONObject(i);
String message = msg.optString("message");
long timestamp = msg.optLong("date", now);
- String person = msg.optString("person", null);
+ String person = msg.optString("person", null);
messages[i] = new Message(message, timestamp, person);
}
diff --git a/src/android/notification/Request.java b/src/android/notification/Request.java
index 807cce0a3..06260bf2b 100644
--- a/src/android/notification/Request.java
+++ b/src/android/notification/Request.java
@@ -258,6 +258,9 @@ private List getSpecialMatchingComponents() {
* Gets the base date from where to calculate the next trigger date.
*/
private Date getBaseDate() {
+ if (spec.has("requestBaseDate")) {
+ return new Date(spec.optLong("requestBaseDate"));
+ } else
if (spec.has("at")) {
return new Date(spec.optLong("at", 0));
} else
diff --git a/src/android/notification/action/Action.java b/src/android/notification/action/Action.java
index 9b7c5057c..9d5440bb8 100644
--- a/src/android/notification/action/Action.java
+++ b/src/android/notification/action/Action.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -22,7 +23,7 @@
package de.appplant.cordova.plugin.notification.action;
import android.content.Context;
-import android.support.v4.app.RemoteInput;
+import androidx.core.app.RemoteInput;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -134,4 +135,4 @@ private String[] getChoices() {
return choices;
}
-}
\ No newline at end of file
+}
diff --git a/src/android/notification/action/ActionGroup.java b/src/android/notification/action/ActionGroup.java
index ec85f0993..7a5675345 100644
--- a/src/android/notification/action/ActionGroup.java
+++ b/src/android/notification/action/ActionGroup.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
diff --git a/src/android/notification/receiver/AbstractClearReceiver.java b/src/android/notification/receiver/AbstractClearReceiver.java
index d6c9e18bb..1272a0d0f 100644
--- a/src/android/notification/receiver/AbstractClearReceiver.java
+++ b/src/android/notification/receiver/AbstractClearReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
diff --git a/src/android/notification/receiver/AbstractClickReceiver.java b/src/android/notification/receiver/AbstractClickReceiver.java
index 4177c1750..8300ad81b 100644
--- a/src/android/notification/receiver/AbstractClickReceiver.java
+++ b/src/android/notification/receiver/AbstractClickReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -21,7 +22,6 @@
package de.appplant.cordova.plugin.notification.receiver;
-import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -29,8 +29,6 @@
import de.appplant.cordova.plugin.notification.Manager;
import de.appplant.cordova.plugin.notification.Notification;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
-import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static de.appplant.cordova.plugin.notification.action.Action.CLICK_ACTION_ID;
import static de.appplant.cordova.plugin.notification.action.Action.EXTRA_ID;
@@ -38,21 +36,22 @@
* Abstract content receiver activity for local notifications. Creates the
* local notification and calls the event functions for further proceeding.
*/
-abstract public class AbstractClickReceiver extends IntentService {
-
- // Holds a reference to the intent to handle.
- private Intent intent;
+abstract public class AbstractClickReceiver extends NotificationTrampolineActivity {
public AbstractClickReceiver() {
- super("LocalNotificationClickReceiver");
+ super();
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ onHandleIntent(getIntent());
}
/**
* Called when local notification was clicked to launch the main intent.
*/
- @Override
protected void onHandleIntent(Intent intent) {
- this.intent = intent;
+ // Holds a reference to the intent to handle.
if (intent == null)
return;
@@ -70,7 +69,6 @@ protected void onHandleIntent(Intent intent) {
return;
onClick(toast, bundle);
- this.intent = null;
}
/**
@@ -87,33 +85,4 @@ protected void onHandleIntent(Intent intent) {
protected String getAction() {
return getIntent().getExtras().getString(EXTRA_ID, CLICK_ACTION_ID);
}
-
- /**
- * Getter for the received intent.
- */
- protected Intent getIntent() {
- return intent;
- }
-
- /**
- * Launch main intent from package.
- */
- protected void launchApp() {
- Context context = getApplicationContext();
- String pkgName = context.getPackageName();
-
- Intent intent = context
- .getPackageManager()
- .getLaunchIntentForPackage(pkgName);
-
- if (intent == null)
- return;
-
- intent.addFlags(
- FLAG_ACTIVITY_REORDER_TO_FRONT
- | FLAG_ACTIVITY_SINGLE_TOP);
-
- context.startActivity(intent);
- }
-
}
diff --git a/src/android/notification/receiver/AbstractNotificationReceiver.java b/src/android/notification/receiver/AbstractNotificationReceiver.java
new file mode 100644
index 000000000..05a4e1a73
--- /dev/null
+++ b/src/android/notification/receiver/AbstractNotificationReceiver.java
@@ -0,0 +1,152 @@
+package de.appplant.cordova.plugin.notification.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Calendar;
+
+import de.appplant.cordova.plugin.localnotification.LocalNotification;
+import de.appplant.cordova.plugin.notification.Manager;
+import de.appplant.cordova.plugin.notification.Notification;
+import de.appplant.cordova.plugin.notification.Options;
+import de.appplant.cordova.plugin.notification.Request;
+import de.appplant.cordova.plugin.notification.util.LaunchUtils;
+
+import static android.content.Context.POWER_SERVICE;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.P;
+import static java.util.Calendar.MINUTE;
+
+/**
+ * The base class for any receiver that is trying to display a notification.
+ */
+abstract public class AbstractNotificationReceiver extends BroadcastReceiver {
+ private final String TAG = "AbstractNotification";
+
+ /**
+ * Perform a notification. All notification logic is here.
+ * Determines whether to dispatch events, autoLaunch the app, use fullScreenIntents, etc.
+ * @param notification reference to the notification to be fired
+ */
+ public void performNotification(Notification notification) {
+ Context context = notification.getContext();
+ Options options = notification.getOptions();
+ Manager manager = Manager.getInstance(context);
+ PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
+ boolean autoLaunch = options.isAutoLaunchingApp() && SDK_INT <= P && !options.useFullScreenIntent();
+
+ int badge = options.getBadgeNumber();
+
+ if (badge > 0) {
+ manager.setBadge(badge);
+ }
+
+ if (options.shallWakeUp()) {
+ wakeUp(notification);
+ }
+
+ if (autoLaunch) {
+ LaunchUtils.launchApp(context);
+ }
+
+ // Show notification if we should (triggerInApp is false)
+ // or if we can't trigger in the app due to:
+ // 1. No autoLaunch configured/supported and app is not running.
+ // 2. Any SDK >= Oreo is asleep (must be triggered here)
+ boolean didShowNotification = false;
+ if (!options.triggerInApp()
+ || (checkAppRunning() && !LocalNotification.isInForeground() )
+ || (!checkAppRunning() && !autoLaunch )
+ ) {
+ didShowNotification = true;
+ notification.show();
+ }
+
+ // run trigger function if triggerInApp() is true
+ // and we did not send a notification.
+ if (options.triggerInApp() && !didShowNotification) {
+ // wake up even if we didn't set it to
+ if (!options.shallWakeUp()) {
+ wakeUp(notification);
+ }
+
+ dispatchAppEvent("trigger", notification);
+ }
+
+ if (!options.isInfiniteTrigger())
+ return;
+
+ Calendar cal = Calendar.getInstance();
+ cal.add(MINUTE, 1);
+
+ Request req = new Request(
+ getOptionsWithBaseDate(options, cal.getTimeInMillis()),
+ cal.getTime()
+ );
+
+ manager.schedule(req, this.getClass());
+ }
+
+ /**
+ * Clone options with base date attached to trigger.
+ * Used so that persisted objects know the last execution time.
+ * @param baseDateMillis base date represented in milliseconds
+ * @return new Options object with base time set in requestBaseDate.
+ */
+ private Options getOptionsWithBaseDate(Options options, long baseDateMillis) {
+ JSONObject optionsDict = options.getDict();
+ try {
+ JSONObject triggerDict = optionsDict.getJSONObject("trigger");
+ triggerDict.put("requestBaseDate", baseDateMillis);
+ optionsDict.remove("trigger");
+ optionsDict.put("trigger", triggerDict);
+ } catch (JSONException e) {
+ Log.e(TAG, "Unexpected error adding requestBaseDate to JSON structure: " + e.toString());
+ }
+ return new Options(optionsDict);
+ }
+
+ /**
+ * Send the application an event using our notification
+ * @param key key for our event in the app
+ * @param notification reference to the notification
+ */
+ abstract public void dispatchAppEvent(String key, Notification notification);
+
+ /**
+ * Check if the application is running.
+ * Should be developed in local class, which has access to things needed for this.
+ * @return whether or not app is running
+ */
+ abstract public boolean checkAppRunning();
+
+ /**
+ * Wakeup the device.
+ *
+ * @param notification The notification used to wakeup the device.
+ * contains context and timeout.
+ */
+ private void wakeUp(Notification notification) {
+ Context context = notification.getContext();
+ Options options = notification.getOptions();
+ String wakeLockTag = context.getApplicationInfo().name + ":LocalNotification";
+ PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
+
+ if (pm == null)
+ return;
+
+ int level = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE;
+
+ PowerManager.WakeLock wakeLock = pm.newWakeLock(level, wakeLockTag);
+
+ wakeLock.setReferenceCounted(false);
+ wakeLock.acquire(options.getWakeLockTimeout());
+ }
+
+}
diff --git a/src/android/notification/receiver/AbstractRestoreReceiver.java b/src/android/notification/receiver/AbstractRestoreReceiver.java
index 753bc2502..85360ee55 100644
--- a/src/android/notification/receiver/AbstractRestoreReceiver.java
+++ b/src/android/notification/receiver/AbstractRestoreReceiver.java
@@ -46,7 +46,7 @@
* the alarms with the AlarmManager since these alarms are lost in case of
* reboot.
*/
-abstract public class AbstractRestoreReceiver extends BroadcastReceiver {
+abstract public class AbstractRestoreReceiver extends AbstractNotificationReceiver {
/**
* Called on device reboot.
diff --git a/src/android/notification/receiver/AbstractTriggerReceiver.java b/src/android/notification/receiver/AbstractTriggerReceiver.java
index 91d7e2a9c..c22547028 100644
--- a/src/android/notification/receiver/AbstractTriggerReceiver.java
+++ b/src/android/notification/receiver/AbstractTriggerReceiver.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -35,7 +36,7 @@
* Abstract broadcast receiver for local notifications. Creates the
* notification options and calls the event functions for further proceeding.
*/
-abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
+abstract public class AbstractTriggerReceiver extends AbstractNotificationReceiver {
/**
* Called when an alarm was triggered.
diff --git a/src/android/notification/receiver/NotificationTrampolineActivity.java b/src/android/notification/receiver/NotificationTrampolineActivity.java
new file mode 100644
index 000000000..34f5e9250
--- /dev/null
+++ b/src/android/notification/receiver/NotificationTrampolineActivity.java
@@ -0,0 +1,43 @@
+package de.appplant.cordova.plugin.notification.receiver;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+
+
+/**
+ * To satitisfy the new android 12 requirement, the broadcast receiver used
+ * to handle click action on a notification, is replaced with a trampoline activity
+ * Note: to handle correctly the case where the application is running in background
+ * while the action is clicked, set
+ *
+ * in the config.xml file.
+ * If you don't add this line, the app is restarted
+ */
+public class NotificationTrampolineActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String packageName = this.getPackageName();
+ Intent launchIntent = this.getPackageManager().getLaunchIntentForPackage(packageName);
+ String mainActivityClassName = launchIntent.getComponent().getClassName();
+ Class mainActivityClass = null;
+ try {
+ mainActivityClass = Class.forName(mainActivityClassName);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ Intent intent = new Intent(this, mainActivityClass);
+
+ // put activity from stack or instance it it doesn't exist
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+}
diff --git a/src/android/notification/trigger/DateTrigger.java b/src/android/notification/trigger/DateTrigger.java
index 04055a079..c8a0116a1 100644
--- a/src/android/notification/trigger/DateTrigger.java
+++ b/src/android/notification/trigger/DateTrigger.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
diff --git a/src/android/notification/trigger/IntervalTrigger.java b/src/android/notification/trigger/IntervalTrigger.java
index 4aeb5e852..89ff8df2b 100644
--- a/src/android/notification/trigger/IntervalTrigger.java
+++ b/src/android/notification/trigger/IntervalTrigger.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
diff --git a/src/android/notification/trigger/MatchTrigger.java b/src/android/notification/trigger/MatchTrigger.java
index 9f753c615..696473250 100644
--- a/src/android/notification/trigger/MatchTrigger.java
+++ b/src/android/notification/trigger/MatchTrigger.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -304,6 +305,7 @@ private boolean setDayOfWeek (Calendar cal) {
return false;
}
+ cal.set(Calendar.SECOND, 0);
cal.set(DAY_OF_WEEK, specials.get(0));
if (matchers.get(3) != null && cal.get(Calendar.MONTH) != month)
diff --git a/src/android/notification/util/AssetProvider.java b/src/android/notification/util/AssetProvider.java
index 6e15825f9..569497fa2 100644
--- a/src/android/notification/util/AssetProvider.java
+++ b/src/android/notification/util/AssetProvider.java
@@ -19,8 +19,8 @@ Licensed to the Apache Software Foundation (ASF) under one
package de.appplant.cordova.plugin.notification.util;
-import android.support.v4.content.FileProvider;
+import androidx.core.content.FileProvider;
public class AssetProvider extends FileProvider {
// Nothing to do here
-}
\ No newline at end of file
+}
diff --git a/src/android/notification/util/AssetUtil.java b/src/android/notification/util/AssetUtil.java
index 2170860dd..0dd8d0638 100644
--- a/src/android/notification/util/AssetUtil.java
+++ b/src/android/notification/util/AssetUtil.java
@@ -2,6 +2,7 @@
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
+ * Contributor Bhumin Bhandari
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
@@ -248,10 +249,6 @@ private void copyFile(InputStream in, FileOutputStream out) {
public int getResId(String resPath) {
int resId = getResId(context.getResources(), resPath);
- if (resId == 0) {
- resId = getResId(Resources.getSystem(), resPath);
- }
-
return resId;
}
@@ -317,7 +314,7 @@ private String getBaseName (String resPath) {
*/
private File getTmpFile () {
// If random UUID is not be enough see
- // https://github.com/LukePulverenti/cordova-plugin-local-notifications/blob/267170db14044cbeff6f4c3c62d9b766b7a1dd62/src/android/notification/AssetUtil.java#L255
+ // https://github.com/LukePulverenti/cordova-plugin-local-notification/blob/267170db14044cbeff6f4c3c62d9b766b7a1dd62/src/android/notification/AssetUtil.java#L255
return getTmpFile(UUID.randomUUID().toString());
}
diff --git a/src/android/notification/util/LaunchUtils.java b/src/android/notification/util/LaunchUtils.java
new file mode 100644
index 000000000..19d3998c7
--- /dev/null
+++ b/src/android/notification/util/LaunchUtils.java
@@ -0,0 +1,67 @@
+package de.appplant.cordova.plugin.notification.util;
+
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Context;
+import android.content.Intent;
+
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import java.util.Random;
+
+public final class LaunchUtils {
+
+ private static int getIntentFlags() {
+ int FLAG_MUTABLE = 33554432; // don't use pendingIntent.FLAG_MUTABLE, use numeric value instead to be able to compile api < 31
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (android.os.Build.VERSION.SDK_INT >= 31) {
+ flags |= FLAG_MUTABLE;
+ }
+ return flags;
+ }
+
+ public static PendingIntent getBroadcastPendingIntent(Context context,
+ Intent intent,
+ int notificationId) {
+ return PendingIntent.getBroadcast(context, notificationId, intent, getIntentFlags());
+ }
+
+ public static PendingIntent getActivityPendingIntent(Context context,
+ Intent intent,
+ int notificationId) {
+ return PendingIntent.getActivity(context, notificationId, intent, getIntentFlags());
+ }
+
+ public static PendingIntent getTaskStackPendingIntent(Context context,
+ Intent intent,
+ int notificationId) {
+ TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
+ taskStackBuilder.addNextIntentWithParentStack(intent);
+ return taskStackBuilder.getPendingIntent(notificationId, getIntentFlags());
+ }
+
+
+ /***
+ * Launch main intent from package.
+ */
+ public static void launchApp(Context context) {
+ String pkgName = context.getPackageName();
+
+ Intent intent = context
+ .getPackageManager()
+ .getLaunchIntentForPackage(pkgName);
+
+ if (intent == null)
+ return;
+
+ intent.addFlags(
+ FLAG_ACTIVITY_REORDER_TO_FRONT
+ | FLAG_ACTIVITY_SINGLE_TOP
+ | FLAG_ACTIVITY_NEW_TASK
+ );
+
+ context.startActivity(intent);
+ }
+}