8000 feat(local-notifications): full Android 12 API 31 support (#253) · NativeScript/plugins@184555c · GitHub
[go: up one dir, main page]

Skip to content

Commit 184555c

Browse files
authored
feat(local-notifications): full Android 12 API 31 support (#253)
BREAKING CHANGES: * plugin has to be imported at application startup no matter the platform * launch config option for notification actions will only work while targeting SDK 30 or lower (starting from Android 12, there's no way to start an activity now from a service). It should be deprecated and removed in future releases.
1 parent 7f543c0 commit 184555c

File tree

9 files changed

+120
-35
lines changed

9 files changed

+120
-35
lines changed

apps/demo/src/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Application } from '@nativescript/core';
22

3+
// uncomment to test local notifications
4+
// import "@nativescript/local-notifications";
5+
36
// uncomment to test background http
47
// import { init } from '@nativescript/background-http';
58
// init();

packages/local-notifications/README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ ns plugin add @nativescript/local-notifications
2626
tns plugin add nativescript-local-notifications@4.2.1
2727
```
2828

29-
## Setup (since plugin version 3.0.0)
29+
## Setup
30+
### Since plugin version 3.0.0
3031

3132
Add this so for iOS 10+ we can do some wiring (set the iOS `UNUserNotificationCenter.delegate`, to be precise).
3233
Not needed if your app loads the plugin on startup anyway.
@@ -43,6 +44,16 @@ import * as LocalNotifications from '@nativescript/local-notifications';
4344
LocalNotifications.hasPermission();
4445
```
4546

47+
### Since plugin version 6.0.0
48+
49+
Both iOS and Android have to register their delegates and lifecycle callbacks respectively. Hence, if your app does not load this plugin at startup you will have to add the following to your app's `app.ts`/`main.ts` file:
50+
51+
```typescript
52+
import '@nativescript/local-notifications';
53+
54+
// ... Bootstrap application
55+
```
56+
4657
### NativeScript-Angular
4758

4859
This plugin is part of the [plugin showcase app](https://github.com/EddyVerbruggen/nativescript-pluginshowcase/tree/master/app/feedback) I built using Angular.
@@ -63,7 +74,7 @@ You can have the `schedule` funtion do that for you automatically (the notificat
6374
You can pass several options to this function, everything is optional:
6475

6576
| option | description |
66-
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77+
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6778
| `id` | A number so you can easily distinguish your notifications. Will be generated if not set. |
6879
| `title` | The title which is shown in the statusbar. Default not set. |
6980
| `subtitle` | Shown below the title on iOS, and next to the App name on Android. Default not set. All android and iOS >= 10 only. |
@@ -90,14 +101,14 @@ You can pass several options to this function, everything is optional:
90101

91102
#### `NotificationAction`
92103

93-
| option | description |
94-
| ------------- | ------------------------------------------------- |
95-
| `id` | An id so you can easily distinguish your actions. |
96-
| `type` | Either `button` or `input`. |
97-
| `title` | The label for `type` = `button`. |
98-
| `launch` | Launch the app when the action completes. |
99-
| `submitLabel` | The submit button label for `type` = `input`. |
100-
| `placeholder` | The placeholder text for `type` = `input`. |
104+
| option | description |
105+
|---------------|------------------------------------------------------------------------------------------------------------------------|
106+
| `id` | An id so you can easily distinguish your actions. |
107+
| `type` | Either `button` or `input`. |
108+
| `title` | The label for `type` = `button`. |
109+
| `launch` | Launch the app when the action completes. This will only work in apps targeting Android 11 or lower (target SDK < 31). |
110+
| `submitLabel` | The submit button label for `type` = `input`. |
111+
| `placeholder` | The placeholder text for `type` = `input`. |
101112

102113
```js
103114
LocalNotifications.schedule([
@@ -139,7 +150,7 @@ These options default to `res://ic_stat_notify` and `res://ic_stat_notify_silhou
139150
and [here's a great guide on how to easily create these icons on Android](https://developer.android.com/studio/write/image-asset-studio).
140151

141152
| Density qualifier | px | dpi |
142-
| ----------------- | ------- | ----------- |
153+
|-------------------|---------|-------------|
143154
| ldpi | 18 × 18 | 120 |
144155
| mdpi | 24 × 24 | 160 |
145156
| hdpi | 36 × 36 | 240 |

packages/local-notifications/native-src/android/app/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
apply plugin: 'com.android.library'
22

33
android {
4-
compileSdkVersion 28
5-
buildToolsVersion "28.0.3"
4+
compileSdkVersion 32
5+
buildToolsVersion "32.0.0"
66

77
defaultConfig {
88
minSdkVersion 17
9-
targetSdkVersion 28
9+
targetSdkVersion 32
1010
versionCode 2
1111
versionName "1.1.1"
1212
}

packages/local-notifications/native-src/android/app/src/main/java/com/telerik/localnotifications/Builder.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import android.app.NotificationChannel;
55
import android.app.NotificationManager;
66
import android.app.PendingIntent;
7+
import android.content.ComponentName;
78
import android.content.Context;
89
import android.content.Intent;
10+
import < 10000 span class=pl-s1>android.content.pm.PackageManager;
911
import android.graphics.Bitmap;
10-
import android.graphics.BitmapFactory;
1112
import android.graphics.Color;
1213
import androidx.annotation.Nullable;
1314
import androidx.core.app.NotificationCompat;
@@ -26,6 +27,8 @@
2627
import java.util.concurrent.ExecutionException;
2728

2829
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
30+
import static android.app.PendingIntent.FLAG_IMMUTABLE;
31+
import static android.app.PendingIntent.FLAG_MUTABLE;
2932

3033
public final class Builder {
3134

@@ -58,7 +61,7 @@ static Notification build(JSONObject options, Context context, int notificationI
5861
channel.setLightColor(getLedColor(options));
5962
}
6063

61-
if(options.has("sound") && options.optString("sound") != "default"){
64+
if(options.has("sound") && !options.optString("sound").equals("default")){
6265
AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
6366
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
6467
.build();
@@ -88,7 +91,7 @@ static Notification build(JSONObject options, Context context, int notificationI
8891
.setTicker(options.optString("ticker", null)); // Let the OS handle the default value for the ticker.
8992

9093
String soundFileName = options.optString("sound", null);
91-
if(soundFileName != null && soundFileName != "default"){
94+
if(soundFileName != null && !soundFileName.equals("default")){
9295
int soundIdentifier = context.getResources().getIdentifier(options.optString("sound"), "raw", context.getApplicationInfo().packageName);
9396
builder.setSound(Uri.parse("android.resource://" + context.getApplicationInfo().packageName + soundIdentifier));
9497
}
@@ -119,7 +122,7 @@ static Notification build(JSONObject options, Context context, int notificationI
119122

120123
applyNotificationLed(options, builder);
121124
applyStyle(options, builder, context);
122-
applyTapReceiver(options, builder, context, notificationID);
125+
applyTapReceiver(builder, context, notificationID);
123126
applyClearReceiver(builder, context, notificationID);
124127
applyActions(options, builder, context, notificationID);
125128

@@ -211,18 +214,20 @@ private static void applyGroup(JSONObject options, NotificationCompat.Builder bu
211214
/**
212215
* Add the intent that handles the event when the notification is clicked (which should launch the app).
213216
*/
214-
private static void applyTapReceiver(JSONObject options, NotificationCompat.Builder builder, Context context, int notificationID) {
215-
final Intent intent = new Intent(context, NotificationActionReceiver.class)
217+
private static void applyTapReceiver(NotificationCompat.Builder builder, Context context, int notificationID) {
218+
PackageManager packageManager = context.getPackageManager();
219+
Intent launchIntent = packageManager.getLaunchIntentForPackage(context.getPackageName());
220+
ComponentName mainActivityRef = launchIntent.getComponent();
221+
final Intent intent = new Intent()
222+
.setComponent(mainActivityRef)
216223
.putExtra(NOTIFICATION_ID, notificationID)
217-
.putExtra("NOTIFICATION_LAUNCH", options.optBoolean("launch", true))
218-
.setAction(Action.CLICK_ACTION_ID)
219-
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
224+
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
220225

221-
builder.setContentIntent(PendingIntent.getService(
226+
builder.setContentIntent(PendingIntent.getActivity(
222227
context,
223228
notificationID,
224229
intent,
225-
FLAG_UPDATE_CURRENT
230+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE : FLAG_UPDATE_CURRENT
226231
));
227232
}
228233

@@ -238,8 +243,8 @@ private static void applyClearReceiver(NotificationCompat.Builder builder, Conte
238243
context,
239244
notificationID,
240245
intent,
241-
FLAG_UPDATE_CURRENT
242-
));
246+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE : FLAG_UPDATE_CURRENT
247+
));
243248
}
244249

245250
private static void applyActions(JSONObject options, NotificationCompat.Builder builder, Context context, int notificationID) {
@@ -297,7 +302,12 @@ private static PendingIntent getPendingIntentForAction(JSONObject options, Conte
297302
.setAction(action.getId())
298303
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
299304

300-
return PendingIntent.getService(context, notificationID, intent, FLAG_UPDATE_CURRENT);
305+
return PendingIntent.getService(
306+
context,
307+
notificationID,
308+
intent,
309+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? FLAG_UPDATE_CURRENT | FLAG_MUTABLE : FLAG_UPDATE_CURRENT
310+
);
301311
}
302312

303313
// Utility methods:

packages/local-notifications/native-src/android/app/src/main/java/com/telerik/localnotifications/LifecycleCallbacks.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22

33
import android.app.Activity;
44
import android.app.Application;
5+
import android.app.NotificationManager;
6+
import android.content.Context;
7+
import android.content.Intent;
58
import android.os.Bundle;
69
import android.util.Log;
710

11+
import org.json.JSONException;
12+
import org.json.JSONObject;
13+
814
/**
915
* Subscribe to the Pause and Resume activity events in order to toggle the plugin's status.
1016
*/
1117
public class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
18+
private static final String TAG = "LocalNotifyLifecycleCb";
1219

1320
private static LifecycleCallbacks callbacks = new LifecycleCallbacks();
1421

@@ -42,6 +49,8 @@ public void onActivityResumed(Activity activity) {
4249

4350
// the application has been resumed-> the push plugin is now in active/foreground state
4451
LocalNotificationsPlugin.isActive = true;
52+
53+
onHandleNotificationIntent(activity.getApplicationContext(), activity.getIntent());
4554
}
4655

4756
public void onActivityCreated(Activity activity, Bundle bundle) {
@@ -58,4 +67,46 @@ public void onActivityStarted(Activity activity) {
5867

5968
public void onActivityStopped(Activity activity) {
6069
}
61-
}
70+
71+
private void onHandleNotificationIntent(Context context, Intent intent) {
72+
if (intent == null || !intent.hasExtra(Builder.NOTIFICATION_ID)) {
73+
return;
74+
}
75+
76+
Bundle bundle = intent.getExtras();
77+
78+
if (bundle == null) {
79+
return;
80+
}
81+
82+
try {
83+
final JSONObject jsonData = new JSONObject();
84+
jsonData.put("event", Builder.NOTIFICATION_ID);
85+
LocalNotificationsPlugin.executeOnMessageClickedCallback(jsonData);
86+
87+
onClick(context, bundle);
88+
} catch (JSONException e) {
89+
Log.e(TAG, e.getMessage(), e);
90+
}
91+
}
92+
93+
private void onClick(Context context, Bundle bundle) throws JSONException {
94+
// Note that for the non-default action this will be empty:
95+
final JSONObject opts = Store.get(context, bundle.getInt(Builder.NOTIFICATION_ID), false);
96+
97+
opts.put("event", "default");
98+
opts.put("foreground", true);
99+
100+
LocalNotificationsPlugin.executeOnMessageReceivedCallback(opts);
101+
102+
if (opts.has("id") && !opts.optBoolean("ongoing", false) && opts.optInt("repeatInterval", 0) == 0) {
103+
int id = opts.getInt("id");
104+
105+
// Clear the notification from the tray, unless it's marker as ongoing/sticky:
106+
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).cancel(id);
107+
108+
// And also unpersist it:
109+
Store.remove(context, id);
110+
}
111+
}
112+
}

packages/local-notifications/native-src/android/app/src/main/java/com/telerik/localnotifications/NotificationActionReceiver.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.app.IntentService;
44
import android.app.NotificationManager;
5+
import android.content.ComponentName;
56
import android.content.Context;
67
import android.content.Intent;
78
import android.content.pm.PackageManager;
@@ -107,8 +108,11 @@ private boolean setTextInput(String action, JSONObject data) throws JSONExceptio
107108
private void forceMainActivityReload() {
108109
PackageManager pm = getPackageManager();
109110
Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
110-
Log.d(TAG, "starting activity for package: " + getApplicationContext().getPackageName());
111-
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
112-
startActivity(launchIntent);
111+
ComponentName mainActivityRef = launchIntent.getComponent();
112+
Log.d(TAG, "starting activity: " + mainActivityRef.toShortString());
113+
Intent mainActivityIntent = new Intent()
114+
.setComponent(mainActivityRef)
115+
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
116+
startActivity(mainActivityIntent);
113117
}
114-
}
118+
}

packages/local-notifications/native-src/android/app/src/main/java/com/telerik/localnotifications/NotificationRestoreReceiver.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.content.BroadcastReceiver;
77
import android.content.Context;
88
import android.content.Intent;
9+
import android.os.Build;
910
import android.util.Log;
1011

1112
import java.util.Date;
@@ -79,7 +80,12 @@ static void scheduleNotification(JSONObject options, Context context) {
7980
.setAction(options.getString("id"))
8081
.putExtra(Builder.NOTIFICATION_ID, notificationID);
8182

82-
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
83+
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
84+
context,
85+
0,
86+
notificationIntent,
87+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_CANCEL_CURRENT
88+
);
8389

8490
if (interval > 0) {
8591
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pendingIntent);

packages/local-notifications/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nativescript/local-notifications",
3-
"version": "5.0.4",
3+
"version": "6.0.0",
44
"description": "The Local Notifications plugin allows your app to show notifications when the app is not running.",
55
"main": "index",
66
"typings": "index.d.ts",
Binary file not shown.

0 commit comments

Comments
 (0)
0