Skip to content

Commit 3f1de8f

Browse files
authored
Merge pull request #182 from Unity-Technologies/innogames-feature/ShowInForeground-feature-for-Android
Show in foreground feature for android
2 parents 99cd1a5 + 889f86e commit 3f1de8f

File tree

10 files changed

+231
-25
lines changed

10 files changed

+231
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
%YAML 1.1
2+
%TAG !u! tag:unity3d.com,2011:
3+
--- !u!114 &11400000
4+
MonoBehaviour:
5+
m_ObjectHideFlags: 0
6+
m_CorrespondingSourceObject: {fileID: 0}
7+
m_PrefabInstance: {fileID: 0}
8+
m_PrefabAsset: {fileID: 0}
9+
m_GameObject: {fileID: 0}
10+
m_Enabled: 1
11+
m_EditorHideFlags: 0
12+
m_Script: {fileID: 11500000, guid: fb7d100a40edf48f99686ea68a90ff4c, type: 3}
13+
m_Name: Not shown in foreground
14+
m_EditorClassIdentifier:
15+
ButtonName: Not shown in foreground
16+
Channel: default_channel
17+
FireInSeconds: 10
18+
NotificationID: 0
19+
Title: Not in foreground
20+
Text: This notifications should not appear when app is in foreground
21+
SmallIcon:
22+
LargeIcon:
23+
NotificationStyle: 0
24+
Color: {r: 0, g: 0, b: 0, a: 1}
25+
Number: -1
26+
ShouldAutoCancel: 0
27+
UsesStopWatch: 0
28+
Group:
29+
GroupSummary: 0
30+
GroupAlertBehaviours: 0
31+
SortKey:
32+
IntentData:
33+
ShowTimestamp: 0
34+
RepeatInterval: 0
35+
ShowInForeground: 0

TestProjects/com.unity.mobile-notifications-sample/Assets/Resources/AndroidNotifications/Not shown in foreground.asset.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TestProjects/com.unity.mobile-notifications-sample/Assets/Scripts/AndroidTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ private void InstantiateAllTestButtons()
149149
SortKey = template.SortKey,
150150
IntentData = template.IntentData,
151151
ShowTimestamp = template.ShowTimestamp,
152-
RepeatInterval = TimeSpan.FromSeconds(template.RepeatInterval)
152+
RepeatInterval = TimeSpan.FromSeconds(template.RepeatInterval),
153+
ShowInForeground = template.ShowInForeground,
153154
},
154155
template.Channel, template.NotificationID
155156
);

TestProjects/com.unity.mobile-notifications-sample/Assets/Scripts/ScriptableObjects/AndroidNotificationTemplate.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class AndroidNotificationTemplate : ScriptableObject
3636
[TextArea] public string IntentData = "";
3737
public bool ShowTimestamp = false;
3838
public long RepeatInterval;
39+
public bool ShowInForeground = true;
3940
#endif
4041
}
4142
}

com.unity.mobile.notifications/Runtime/Android/AndroidNotification.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,21 @@ public DateTime CustomTimestamp
177177
}
178178
}
179179

180+
/// <summary>
181+
/// Set this notification to be shown when app is in the foreground (default: true).
182+
/// </summary>
183+
public bool ShowInForeground
184+
{
185+
get => !m_SilentInForeground;
186+
set => m_SilentInForeground = !value;
187+
}
188+
180189
internal bool ShowCustomTimestamp { get; set; }
181190

182191
private Color m_Color;
183192
private TimeSpan m_RepeatInterval;
184193
private DateTime m_CustomTimestamp;
194+
private bool m_SilentInForeground;
185195

186196
/// <summary>
187197
/// Create a notification struct with all optional fields set to default values.
@@ -212,6 +222,7 @@ public AndroidNotification(string title, string text, DateTime fireTime)
212222
m_RepeatInterval = (-1L).ToTimeSpan();
213223
m_Color = new Color(0, 0, 0, 0);
214224
m_CustomTimestamp = (-1L).ToDatetime();
225+
m_SilentInForeground = false;
215226
}
216227

217228
/// <summary>

com.unity.mobile.notifications/Runtime/Android/AndroidNotificationCenter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct NotificationManagerJni
5151
public AndroidJavaObject KEY_REPEAT_INTERVAL;
5252
public AndroidJavaObject KEY_NOTIFICATION;
5353
public AndroidJavaObject KEY_SMALL_ICON;
54+
public AndroidJavaObject KEY_SHOW_IN_FOREGROUND;
5455

5556
private JniMethodID getNotificationFromIntent;
5657
private JniMethodID setNotificationIcon;
@@ -88,6 +89,7 @@ public NotificationManagerJni(AndroidJavaClass clazz, AndroidJavaObject obj)
8889
KEY_REPEAT_INTERVAL = clazz.GetStatic<AndroidJavaObject>("KEY_REPEAT_INTERVAL");
8990
KEY_NOTIFICATION = clazz.GetStatic<AndroidJavaObject>("KEY_NOTIFICATION");
9091
KEY_SMALL_ICON = clazz.GetStatic<AndroidJavaObject>("KEY_SMALL_ICON");
92+
KEY_SHOW_IN_FOREGROUND = clazz.GetStatic<AndroidJavaObject>("KEY_SHOW_IN_FOREGROUND");
9193

9294
CollectMethods(clazz);
9395
#else
@@ -98,6 +100,7 @@ public NotificationManagerJni(AndroidJavaClass clazz, AndroidJavaObject obj)
98100
KEY_REPEAT_INTERVAL = null;
99101
KEY_NOTIFICATION = null;
100102
KEY_SMALL_ICON = null;
103+
KEY_SHOW_IN_FOREGROUND = null;
101104
#endif
102105
}
103106

@@ -417,6 +420,7 @@ struct BundleJni
417420
JniMethodID getInt;
418421
JniMethodID getLong;
419422
JniMethodID getString;
423+
JniMethodID putBoolean;
420424
JniMethodID putInt;
421425
JniMethodID putLong;
422426
JniMethodID putString;
@@ -430,6 +434,7 @@ public void CollectJni()
430434
getInt = JniApi.FindMethod(clazz, "getInt", "(Ljava/lang/String;I)I", false);
431435
getLong = JniApi.FindMethod(clazz, "getLong", "(Ljava/lang/String;J)J", false);
432436
getString = JniApi.FindMethod(clazz, "getString", "(Ljava/lang/String;)Ljava/lang/String;", false);
437+
putBoolean = JniApi.FindMethod(clazz, "putBoolean", "(Ljava/lang/String;Z)V", false);
433438
putInt = JniApi.FindMethod(clazz, "putInt", "(Ljava/lang/String;I)V", false);
434439
putLong = JniApi.FindMethod(clazz, "putLong", "(Ljava/lang/String;J)V", false);
435440
putString = JniApi.FindMethod(clazz, "putString", "(Ljava/lang/String;Ljava/lang/String;)V", false);
@@ -461,6 +466,11 @@ public string GetString(AndroidJavaObject bundle, AndroidJavaObject key)
461466
return bundle.Call<string>(getString, key);
462467
}
463468

469+
public void PutBoolean(AndroidJavaObject bundle, AndroidJavaObject key, bool value)
470+
{
471+
bundle.Call(putBoolean, key, value);
472+
}
473+
464474
public void PutInt(AndroidJavaObject bundle, AndroidJavaObject key, int value)
465475
{
466476
bundle.Call(putInt, key, value);
@@ -914,6 +924,7 @@ public static AndroidJavaObject CreateNotificationBuilder(int id, AndroidNotific
914924
s_Jni.Bundle.PutInt(extras, s_Jni.NotificationManager.KEY_ID, id);
915925
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, notification.RepeatInterval.ToLong());
916926
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, fireTime);
927+
s_Jni.Bundle.PutBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, notification.ShowInForeground);
917928
if (!string.IsNullOrEmpty(notification.IntentData))
918929
s_Jni.Bundle.PutString(extras, s_Jni.NotificationManager.KEY_INTENT_DATA, notification.IntentData);
919930
}
@@ -941,6 +952,7 @@ internal static AndroidNotificationIntentData GetNotificationData(AndroidJavaObj
941952
notification.UsesStopwatch = s_Jni.Bundle.GetBoolean(extras, s_Jni.Notification.EXTRA_SHOW_CHRONOMETER, false);
942953
notification.FireTime = s_Jni.Bundle.GetLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, -1L).ToDatetime();
943954
notification.RepeatInterval = s_Jni.Bundle.GetLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, -1L).ToTimeSpan();
955+
notification.ShowInForeground = s_Jni.Bundle.GetBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, true);
944956

945957
if (s_Jni.Bundle.ContainsKey(extras, s_Jni.Notification.EXTRA_BIG_TEXT))
946958
notification.Style = NotificationStyle.BigTextStyle;

com.unity.mobile.notifications/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationManager.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.unity.androidnotifications;
22

33
import android.app.Activity;
4+
import android.app.ActivityManager;
45
import android.app.AlarmManager;
56
import android.app.Notification;
67
import android.app.NotificationManager;
@@ -21,6 +22,8 @@
2122
import android.service.notification.StatusBarNotification;
2223
import android.util.Log;
2324

25+
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
26+
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
2427
import static android.app.Notification.VISIBILITY_PUBLIC;
2528

2629
import java.lang.Integer;
@@ -58,6 +61,7 @@ public class UnityNotificationManager extends BroadcastReceiver {
5861
protected static final String KEY_NOTIFICATION_ID = "com.unity.NotificationID";
5962
protected static final String KEY_SMALL_ICON = "smallIcon";
6063
protected static final String KEY_CHANNEL_ID = "channelID";
64+
protected static final String KEY_SHOW_IN_FOREGROUND = "com.unity.showInForeground";
6165
protected static final String KEY_NOTIFICATION_DISMISSED = "com.unity.NotificationDismissed";
6266

6367
protected static final String NOTIFICATION_CHANNELS_SHARED_PREFS = "UNITY_NOTIFICATIONS";
@@ -712,7 +716,10 @@ public void onReceive(Context context, Intent intent) {
712716

713717
// Call the system notification service to notify the notification.
714718
protected static void notify(Context context, int id, Notification notification) {
715-
getNotificationManager(context).notify(id, notification);
719+
boolean showInForeground = notification.extras.getBoolean(KEY_SHOW_IN_FOREGROUND, true);
720+
if (!isInForeground() || showInForeground) {
721+
getNotificationManager(context).notify(id, notification);
722+
}
716723
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) synchronized (UnityNotificationManager.class) {
717724
mVisibleNotifications.add(Integer.valueOf(id));
718725
}
@@ -841,6 +848,12 @@ public static String getNotificationChannelId(Notification notification) {
841848
return null;
842849
}
843850

851+
private static boolean isInForeground() {
852+
ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
853+
ActivityManager.getMyMemoryState(appProcessInfo);
854+
return (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE);
855+
}
856+
844857
public static Notification getNotificationFromIntent(Context context, Intent intent) {
845858
Object notification = getNotificationOrBuilderForIntent(context, intent);
846859
if (notification == null)

com.unity.mobile.notifications/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationUtilities.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,20 @@
2929
import static com.unity.androidnotifications.UnityNotificationManager.KEY_NOTIFICATION;
3030
import static com.unity.androidnotifications.UnityNotificationManager.KEY_REPEAT_INTERVAL;
3131
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SMALL_ICON;
32+
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SHOW_IN_FOREGROUND;
3233
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
3334

3435
public class UnityNotificationUtilities {
36+
/*
37+
We serialize notifications and save them to shared prefs, so that if app is killed, we can recreate them.
38+
The serialized BLOB starts with a four byte magic number descibing serialization type, followed by an integer version.
39+
IMPORTANT: IF YOU DO A CHANGE THAT AFFECTS THE LAYOUT, BUMP THE VERSION, AND ENSURE OLD VERSION STILL DESERIALIZES. ADD TEST.
40+
In real life app can get updated having old serialized notifications present, so we should be able to deserialize them.
41+
*/
3542
// magic stands for "Unity Mobile Notifications Notification"
3643
private static final byte[] UNITY_MAGIC_NUMBER = new byte[] { 'U', 'M', 'N', 'N'};
3744
private static final byte[] UNITY_MAGIC_NUMBER_PARCELLED = new byte[] { 'U', 'M', 'N', 'P'};
38-
private static final int NOTIFICATION_SERIALIZATION_VERSION = 0;
45+
private static final int NOTIFICATION_SERIALIZATION_VERSION = 1;
3946
private static final int INTENT_SERIALIZATION_VERSION = 0;
4047

4148
private static final String SAVED_NOTIFICATION_PRIMARY_KEY = "data";
@@ -144,6 +151,7 @@ private static boolean serializeNotificationCustom(Notification notification, Da
144151
out.writeBoolean(notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
145152
out.writeBoolean(showWhen);
146153
serializeString(out, notification.extras.getString(KEY_INTENT_DATA));
154+
out.writeBoolean(notification.extras.getBoolean(KEY_SHOW_IN_FOREGROUND, true));
147155
}
148156

149157
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? null : notification.getChannelId());
@@ -249,7 +257,7 @@ private static Notification deserializeNotificationParcelable(DataInputStream in
249257
if (!readAndCheckMagicNumber(in, UNITY_MAGIC_NUMBER_PARCELLED))
250258
return null;
251259
int version = in.readInt();
252-
if (version <0 || version > INTENT_SERIALIZATION_VERSION)
260+
if (version < 0 || version > INTENT_SERIALIZATION_VERSION)
253261
return null;
254262
Intent intent = deserializeParcelable(in);
255263
Notification notification = intent.getParcelableExtra(KEY_NOTIFICATION);
@@ -275,7 +283,7 @@ private static Notification.Builder deserializeNotificationCustom(DataInputStrea
275283
int id = 0;
276284
String title, text, smallIcon, largeIcon, bigText, intentData;
277285
long fireTime, repeatInterval;
278-
boolean usesStopWatch, showWhen;
286+
boolean usesStopWatch, showWhen, showInForeground = true;
279287
Bundle extras = null;
280288
try {
281289
extras = deserializeParcelable(in);
@@ -296,6 +304,8 @@ private static Notification.Builder deserializeNotificationCustom(DataInputStrea
296304
usesStopWatch = in.readBoolean();
297305
showWhen = in.readBoolean();
298306
intentData = deserializeString(in);
307+
if (version > 0)
308+
showInForeground = in.readBoolean();
299309
} else {
300310
title = extras.getString(Notification.EXTRA_TITLE);
301311
text = extras.getString(Notification.EXTRA_TEXT);
@@ -335,6 +345,7 @@ private static Notification.Builder deserializeNotificationCustom(DataInputStrea
335345
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, repeatInterval);
336346
if (intentData != null)
337347
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
348+
builder.getExtras().putBoolean(KEY_SHOW_IN_FOREGROUND, showInForeground);
338349
}
339350
if (title != null)
340351
builder.setContentTitle(title);
@@ -566,4 +577,53 @@ private static Notification.Builder recoverBuilderPreNougat(Context context, Not
566577

567578
return builder;
568579
}
580+
581+
// for testing purposes: copy-paste of what serialization was in version 0
582+
// except for hardcoded version 0
583+
private static boolean serializeNotificationCustom_v0(Notification notification, DataOutputStream out) {
584+
try {
585+
out.write(UNITY_MAGIC_NUMBER);
586+
out.writeInt(0); // NOTIFICATION_SERIALIZATION_VERSION
587+
588+
// serialize extras
589+
boolean showWhen = notification.extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false);
590+
byte[] extras = serializeParcelable(notification.extras);
591+
out.writeInt(extras == null ? 0 : extras.length);
592+
if (extras != null && extras.length > 0)
593+
out.write(extras);
594+
else {
595+
// parcelling may fail in case it contains binder object, when that happens serialize manually what we care about
596+
out.writeInt(notification.extras.getInt(KEY_ID));
597+
serializeString(out, notification.extras.getString(Notification.EXTRA_TITLE));
598+
serializeString(out, notification.extras.getString(Notification.EXTRA_TEXT));
599+
serializeString(out, notification.extras.getString(KEY_SMALL_ICON));
600+
serializeString(out, notification.extras.getString(KEY_LARGE_ICON));
601+
out.writeLong(notification.extras.getLong(KEY_FIRE_TIME, -1));
602+
out.writeLong(notification.extras.getLong(KEY_REPEAT_INTERVAL, -1));
603+
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? null : notification.extras.getString(Notification.EXTRA_BIG_TEXT));
604+
out.writeBoolean(notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
605+
out.writeBoolean(showWhen);
606+
serializeString(out, notification.extras.getString(KEY_INTENT_DATA));
607+
}
608+
609+
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? null : notification.getChannelId());
610+
Integer color = UnityNotificationManager.getNotificationColor(notification);
611+
out.writeBoolean(color != null);
612+
if (color != null)
613+
out.writeInt(color);
614+
out.writeInt(notification.number);
615+
out.writeBoolean(0 != (notification.flags & Notification.FLAG_AUTO_CANCEL));
616+
serializeString(out, notification.getGroup());
617+
out.writeBoolean(0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY));
618+
out.writeInt(UnityNotificationManager.getNotificationGroupAlertBehavior(notification));
619+
serializeString(out, notification.getSortKey());
620+
if (showWhen)
621+
out.writeLong(notification.when);
622+
623+
return true;
624+
} catch (Exception e) {
625+
Log.e(TAG_UNITY, "Failed to serialize notification", e);
626+
return false;
627+
}
628+
}
569629
}

com.unity.mobile.notifications/Tests/Runtime/Android/AndroidNotificationSendingTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,27 @@ public IEnumerator SendNotification_CanReschedule()
329329

330330
Assert.AreEqual(1, currentHandler.receivedNotificationCount);
331331
}
332+
333+
[UnityTest]
334+
[UnityPlatform(RuntimePlatform.Android)]
335+
public IEnumerator SendNotificationNotShownInForeground_IsDeliveredButNotShown()
336+
{
337+
var n = new AndroidNotification();
338+
n.Title = "SendNotificationNotShownInForeground_ISDeliveredButNotShown";
339+
n.Text = "SendNotificationNotShownInForeground_ISDeliveredButNotShown Text";
340+
n.FireTime = System.DateTime.Now;
341+
n.ShowInForeground = false;
342+
343+
Debug.LogWarning("SendNotificationNotShownInForeground_ISDeliveredButNotShown sends notification");
344+
345+
int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
346+
yield return WaitForNotification(5.0f);
347+
348+
Debug.LogWarning("SendNotificationNotShownInForeground_ISDeliveredButNotShown sends completed");
349+
350+
Assert.AreEqual(1, currentHandler.receivedNotificationCount);
351+
yield return new WaitForSeconds(2.0f); // give some time, since on some devices we don't immediately get infor on delivered notifications
352+
var status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
353+
Assert.AreEqual(NotificationStatus.Unknown, status); // status should be unknown, rather than Delivered
354+
}
332355
}

0 commit comments

Comments
 (0)