Solved Receiving GCM notifications on Android - system reports that the app is closed
-
Hello.
I work on a messaging app for Android using Qt 5.5.
I try to integrate GCM notifications using the Java source code provided by Google on github.com:https://github.com/googlesamples/google-services/tree/master/android/gcm/app/src/main
Everything seems to work:
My app retrieves a GCM token from Google and sends it to my messaging server and my Android device receives GCM messages and generates notifications from them playing a notification sound and displaying some text. If the user clicks on the notification my app is brought to the foreground. All works as expected.This works fine as long as my app is running in foreground or background - I mean as long as the app is listed in Android's "opened apps view" - I don't know the exact term for that - where you can switch to another opened app. If the user closed my app there (normal swipe) and the system receives a GCM message for my app then the system displays a kind of MessageBox saying that my app is closed. The system does not generate a notification message.
Is there some Java code that can be added somewhere to cover this case and generate the notification?
I hope I described the problem clear enough.
Please have a look at some project's files.The Android package name of my app is "net.singlejungle.singlejungle" and my custom GCM Java files are in the package "org.qtproject.example". The Java class "MyJniNatives" is used to call C++ functions.
AndroidManifest.xml:
<?xml version="1.0"?> <manifest package="net.singlejungle.singlejungle" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.0" android:versionCode="2" android:installLocation="auto"> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22"/> <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <permission android:name="net.singlejungle.singlejungle.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <uses-permission android:name="net.singlejungle.singlejungle.permission.C2D_MESSAGE"/> <uses-permission android:name="android.permission.VIBRATE"/> <application android:hardwareAccelerated="true" android:name="org.qtproject.example.MyApplication" android:label="SingleJungle" android:icon="@drawable/icon"> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/> <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.example.Vibrate" android:label="SingleJungle" android:screenOrientation="unspecified" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <meta-data android:name="android.app.lib_name" android:value="FlirtApp"/> <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> <meta-data android:name="android.app.repository" android:value="default"/> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> <!-- Deploy Qt libs as part of package --> <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/> <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/> <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/> <!-- Run with local libs --> <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/> <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> <meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/> <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/> <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/> <!-- Messages maps --> <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/> <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/> <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/> <!-- Messages maps --> <!-- Splash screen --> <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/> <!-- Splash screen --> <!-- Background running --> <!-- Warning: changing this value to true may cause unexpected crashes if the application still try to draw after "applicationStateChanged(Qt::ApplicationSuspended)" signal is sent! --> <meta-data android:name="android.app.background_running" android:value="false"/> <!-- Background running --> </activity> <service android:name="org.qtproject.example.RegistrationIntentService" android:exported="false"/> <service android:name="org.qtproject.example.MyInstanceIDListenerService" android:exported="false"> <intent-filter> <action android:name="com.google.android.gms.iid.InstanceID"/> </intent-filter> </service> <receiver android:name="com.google.android.gms.gcm.GcmReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> <category android:name="net.singlejungle.singlejungle"/> </intent-filter> </receiver> <service android:name="org.qtproject.example.MyGcmListenerService" android:exported="false"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> </intent-filter> </service> </application> <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application. Remove the comment if you do not require these default features. --> <!-- %%INSERT_FEATURES --> <!-- %%INSERT_PERMISSIONS --> </manifest>
MyApplication.java:
package org.qtproject.example; import android.content.Context; public class MyApplication extends org.qtproject.qt5.android.bindings.QtApplication { private static Context context; public void onCreate() { super.onCreate(); MyApplication.context = getApplicationContext(); } public static Context getAppContext() { return MyApplication.context; } }
Vibrate.java (the main activity):
package org.qtproject.example; import android.content.Context; import android.os.Vibrator; import android.app.Activity; import android.os.Bundle; import android.R; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.ProgressBar; import android.widget.TextView; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; public class Vibrate extends org.qtproject.qt5.android.bindings.QtActivity { // start GCM private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; private static final String TAG = "MainActivity"; private BroadcastReceiver mRegistrationBroadcastReceiver; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); mRegistrationBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); boolean sentToken = sharedPreferences.getBoolean(QuickstartPreferences.SENT_TOKEN_TO_SERVER, false); if (sentToken) { MyJniNatives.log("GCM token retrieved"); } else { MyJniNatives.log("an error occurred retrieving a GCM token"); } } }; if (checkPlayServices()) { // Start IntentService to register this application with GCM. Intent intent = new Intent(this, RegistrationIntentService.class); startService(intent); } } @Override protected void onResume() { super.onResume(); LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver, new IntentFilter(QuickstartPreferences.REGISTRATION_COMPLETE)); } @Override protected void onPause() { LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver); super.onPause(); } /** * Check the device to make sure it has the Google Play Services APK. If * it doesn't, display a dialog that allows users to download the APK from * the Google Play Store or enable it in the device's system settings. */ private boolean checkPlayServices() { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); int resultCode = apiAvailability.isGooglePlayServicesAvailable(this); if (resultCode != ConnectionResult.SUCCESS) { if (apiAvailability.isUserResolvableError(resultCode)) { apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).show(); } else { Log.i(TAG, "This device is not supported."); finish(); } return false; } return true; } // end GCM // start vibrate public static Vibrator m_vibrator; public static Vibrate m_istance; public Vibrate() { m_istance = this; } public static void start(int x) { if (m_vibrator == null) { if (m_istance != null) { m_vibrator = (Vibrator) m_istance.getSystemService(Context.VIBRATOR_SERVICE); m_vibrator.vibrate(x); } } else m_vibrator.vibrate(x); MyJniNatives.log(new String("vibrated")); } // end vibrate }
MyGcmListenerService.java:
package org.qtproject.example; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.util.Log; import net.singlejungle.singlejungle.R; import com.google.android.gms.gcm.GcmListenerService; public class MyGcmListenerService extends GcmListenerService { private static final String TAG = "MyGcmListenerService"; /** * Called when message is received. * * @param from SenderID of the sender. * @param data Data bundle containing message data as key/value pairs. * For Set of keys use data.keySet(). */ // [START receive_message] @Override public void onMessageReceived(String from, Bundle data) { String message = data.getString("message"); Log.d(TAG, "From: " + from); Log.d(TAG, "Message: " + message); if (from.startsWith("/topics/")) { // message received from some topic. } else { // normal downstream message. } // [START_EXCLUDE] /** * Production applications would usually process the message here. * Eg: - Syncing with server. * - Store message in local database. * - Update UI. */ /** * In some cases it may be useful to show a notification indicating to the user * that a message was received. */ sendNotification(message); // [END_EXCLUDE] } // [END receive_message] /** * Create and show a simple notification containing the received GCM message. * * @param message GCM message received. */ private void sendNotification(String message) { Intent intent = new Intent(this, Vibrate.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT); String processedNotificationMessage = MyJniNatives.processNotificationMessage(message); Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.icon) .setContentTitle("Single-Jungle") .setContentText(processedNotificationMessage) .setAutoCancel(true) .setSound(defaultSoundUri) .setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()); } }
-
I found and solved the problem:
In the file MyGcmListenerService.java I called the C++-function "processNotificationMessage()" via JNI (Java Native Interface).
String processedNotificationMessage = MyJniNatives.processNotificationMessage(message);
I suppose the C++-code is executed on a separate application thread or system process. If a GCM message is received by Android and the app is not running yet, the C++-code called via JNI cannot be executed because this thread/process for C++ execution is missing.
I solved the problem by implementing the C++ code in Java avoiding the usage of JNI in this case.