I am creating a webview using Android Studio Giraffe and I am having this strange issue with FCM Push Notification. I am unable to receive notifications on real device when the app is killed. It works fine when the app is running (foreground) or minimized (background) but when the app is killed the app doesn't receive notifications. Also, the notifications that are already there which were received when the app was running also vanish as soon as I kill the app. Strangely, in the Android Emulator with Android 13 API 33, everything seems to work perfectly fine. I receive notifications in all the three states (foreground, background and killed). Also, the already received notifications remain even if I kill the app. On real device, its also working perfectly fine upto Android 12. I tested on two real phones running Android 12 (Moto G60) and Android 10 (Honor 8x). However, the phone with Android 13 (Asus Zenfone 8z), is facing all the issues described above. I have requested runtime permissions to allow notifications in Android 13. Below are my codes:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Kelvie"
tools:targetApi="31">
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".WebViewActivity"
android:exported="true">
<!-- Add this intent filter to ensure the activity can be launched from an Intent -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".FCM"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
MainActivity.java
package com.kelvienew.app;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.FirebaseApp;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize Firebase
FirebaseApp.initializeApp(this);
// Request FCM token (if needed)
FCM.requestFCMToken(getApplicationContext());
// Start WebViewActivity or other operations
startWebViewActivity();
}
private void startWebViewActivity() {
Intent intent = new Intent(this, WebViewActivity.class);
startActivity(intent);
finish();
}
}
WebViewActivity.java
package com.kelvienew.app;
import static com.kelvienew.app.FCM.createNotificationChannel;
import android.Manifest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class WebViewActivity extends AppCompatActivity {
private WebView webView;
private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 123;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
webView = findViewById(R.id.webView);
// Enable JavaScript and other settings as needed
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
// Load your URL
loadUrl();
// Handle push notification data when the app is opened
handlePushNotificationData(getIntent());
// Check and request notification permission
checkNotificationPermission();
}
private void loadUrl() {
// Load your URL
webView.loadUrl("https://www.google.com");
}
private void handlePushNotificationData(Intent intent) {
if (intent != null && intent.getExtras() != null) {
for (String key : intent.getExtras().keySet()) {
Object value = intent.getExtras().get(key);
// Handle the data as needed
// For example, log the data
Log.d("FCM", "Key: " + key + ", Value: " + value);
}
}
}
private void checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (this.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.POST_NOTIFICATIONS}, 123);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
// Check if the permission was granted
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted, you can now create notification channels
// or perform any other operations that require this permission
createNotificationChannel();
} else {
// Permission denied, handle accordingly (e.g., show a message to the user)
Toast.makeText(this, "By turning off notifications, you will not be notified for new messages.", Toast.LENGTH_LONG).show();
}
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = getSystemService(NotificationManager.class);
// Define the notification channel
String channelId = "KELVIECH1"; // Replace with your desired channel ID
CharSequence channelName = "Kelvie Messages"; // Replace with your desired channel name
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
channel.setDescription("Your channel description"); // Replace with your desired channel description
// Optional: Configure additional channel settings (e.g., lights, vibration)
channel.enableLights(true);
channel.setLightColor(Color.RED);
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500});
// Create the notification channel
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
// Now you can use this channel to send notifications
// For example: sendNotification(channelId, "Your Notification Title", "Your Notification Body");
}
}
}
}
FCM.java
package com.kelvienew.app;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;
public class FCM extends FirebaseMessagingService {
private static final String TAG_PREFIX = "KelvieFCM";
private static final String CHANNEL_ID = "KELVIECH1";
private static final String WAKE_LOCK_TAG = TAG_PREFIX + ":WakeLockTag";
private static PowerManager.WakeLock wakeLock;
private static final String TAG = "FCMTokenSender";
private static final String PHP_PAGE_URL = "http://192.168.29.175/kelvie/sendtoken.php"; // Replace with your PHP page URL
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
if (remoteMessage.getNotification() != null) {
String title = remoteMessage.getNotification().getTitle();
String body = remoteMessage.getNotification().getBody();
// You can customize this part based on your notification payload
showNotification(getApplicationContext(), title, body);
}
if (remoteMessage.getData().size() > 0) {
Log.d(TAG_PREFIX, "Message data payload: " + remoteMessage.getData());
// Handle the data payload here
handleDataPayload(remoteMessage.getData());
// You may need to customize this part based on your payload
String title = remoteMessage.getData().get("title");
String body = remoteMessage.getData().get("body");
// Acquire a wake lock before processing the message
acquireWakeLock();
// Show the notification directly without explicit Intent
showNotification(getApplicationContext(), title, body);
// Release the wake lock after processing
releaseWakeLock();
}
}
@Override
public void onNewToken(@NonNull String token) {
super.onNewToken(token);
Log.d(TAG_PREFIX, "Refreshed token: " + token);
// If you want to send the new token to your server or perform any other action, do it here.
}
public static void showNotification(Context context, String title, String messageBody) {
createNotificationChannel(context);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setPriority(NotificationCompat.PRIORITY_HIGH);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.notify(0, notificationBuilder.build());
}
}
public static void createNotificationChannel(Context context) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Kelvie Notifications",
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription("Kelvie Notification Messages");
channel.enableLights(true);
channel.setLightColor(Color.RED); // Replace with your color
channel.enableVibration(true);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
private void handleDataPayload(Map<String, String> data) {
// Implement your logic to handle the data payload
// You can access the data using the keys from the map
// For example:
String key1 = data.get("title");
String key2 = data.get("body");
// Perform actions based on the received data
Log.d(TAG_PREFIX, "Key1: " + key1 + ", Key2: " + key2);
}
public static void requestFCMToken(Context context) {
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String token = task.getResult();
Log.d(TAG_PREFIX, "FCM Token: " + token);
// Call the method to send the token to your server
sendTokenToServer(context, token);
} else {
Log.w(TAG_PREFIX, "Failed to get FCM token", task.getException());
}
});
}
public static void sendTokenToServer(Context context, String token) {
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest(Request.Method.POST, PHP_PAGE_URL,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d(TAG, "Token sent successfully");
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error sending token: " + error.getMessage());
}
}) {
@Override
protected java.util.Map<String, String> getParams() {
java.util.Map<String, String> params = new java.util.HashMap<>();
params.put("fcm_token", token);
return params;
}
};
// Add the request to the RequestQueue.
queue.add(stringRequest);
}
private void acquireWakeLock() {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
// Set the timeout in milliseconds (adjust as needed)
long timeout = 10 * 1000; // 10 seconds as an example, adjust as needed
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
// Acquire the wakelock with a timeout
wakeLock.acquire(timeout);
}
}
private void releaseWakeLock() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
build.gradle (Module :app)
plugins {
id 'com.android.application'
id 'com.google.gms.google-services'
}
android {
namespace 'com.kelvienew.app'
compileSdk 34
defaultConfig {
applicationId "com.kelvienew.app"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.android.gms:play-services-maps:18.2.0'
implementation 'com.android.volley:volley:1.2.1'
}
On my real device where I am facing the issues, I have enabled manually all the settings it could need like Unrestricted battery access, override do not disturb etc. Nothing works. I had a plan to downgrade to Android 12 due to this issue but that would not solve the problem for other users who are using Android 13 and might still not receive important notifications. I am literally stuck on it for days now. Read a lot of articles, stackoverflow questions and even tried ChatGPT. Nothing really helped me fix it actually. Any help would be appreaciated. Thanks.