diff --git a/README.md b/README.md
index 17bef17..163a409 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,43 @@
-# Titanium Module for Google Cloud Messaging Push Notifications for Android #
+# Titanium Module for Firebase Cloud Messaging Push Notifications for Android #
-A Titanium module for registering a device with Google Cloud Messaging and handling push notifications sent to the device.
+A Titanium module for registering a device with Firebase Cloud Messaging and handling push notifications sent to the device.
[](http://gitt.io/component/nl.vanvianen.android.gcm)
Read the [documentation](https://github.com/morinel/gcmpush/blob/master/documentation/index.md).
-To build, create a `build.properties` file with the following content:
+## Quickstart
+
+1. Install this module using Axway Studio or via the CLI with `gittio install nl.vanvianen.android.gcm`
+2. Set up your [Firebase project](https://console.firebase.google.com/).
+3. Download the `google-services.json` file and put it in `PROJECT_FOLDER/app/assets/android/` (or `PROJECT_FOLDER/Resources/android/` for non-Alloy projects).
+4. You are ready to receive Android push notifications! Check the [documentation](https://github.com/morinel/gcmpush/blob/master/documentation/index.md) for further instructions and examples.
+
+## GCM to FCM Migration
+
+If you are upgrading this module to version 3.0+, you will need to migrate to Firebase (Google will discontinue GCM in April 2019):
+
+1. In the [Firebase console](https://console.firebase.google.com/), select __Add Project__
+2. Select your GCM project from the list of existing Google Cloud Projects, and select __Add Firebase__
+3. In the Firebase welcome screen, select __Add Firebase to your Android App__
+4. Provide your package name (and optionally an SHA1 from your keystore) and select __Add App__. A new `google-services.json` file for your Firebase app is downloaded. Place this file in `PROJECT_FOLDER/app/assets/android/` (or `PROJECT_FOLDER/Resources/android/` for non-Alloy projects).
+5. Update to version 3.0+ of this module
+6. Check the [Upgrade Guide](https://github.com/morinel/gcmpush/blob/master/documentation/index.md#version-3-upgrade-guide) for instructions about what needs to be altered in your code
+
+## Build
+
+To build this module, you will need the prerequisites described in the [Android Module Project documentation](https://docs.appcelerator.com/platform/latest/#!/guide/Android_Module_Project).
+
+Then, simply `cd` into the module directory and run the following command:
```
-titanium.platform=/Users/###USER###/Library/Application Support/Titanium/mobilesdk/osx/7.0.0.GA/android
-android.platform=/Users/###USER###/Library/Android/sdk/platforms/android-26
-google.apis=/Users/###USER###/Library/Android/sdk/add-ons/addon-google_apis-google-23
-android.ndk=/Users/###USER###/Library/Android/ndk
+$ appc ti build -p android --build-only
```
-Make sure your paths are correct for your system setup. Then run:
+OR, if you are using just the open-source Titanium SDK:
```
-$ appc run -p android --build-only
+$ ti build -p android --build-only
```
A zip file will be created in the `dist` folder.
diff --git a/build.properties b/build.properties
deleted file mode 100644
index 8621a43..0000000
--- a/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-titanium.platform=/Users/jeroen/Library/Application Support/Titanium/mobilesdk/osx/7.0.0.GA/android
-android.platform=/Users/jeroen/Library/Android/sdk/platforms/android-26
-google.apis=/Users/jeroen/Library/Android/sdk/add-ons/addon-google_apis-google-23
-android.ndk=/Users/jeroen/Library/Android/ndk-r16b
diff --git a/build.xml b/build.xml
deleted file mode 100644
index f015cba..0000000
--- a/build.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- Ant build script for Titanium Android module gcm
-
-
-
-
-
-
-
diff --git a/documentation/index.md b/documentation/index.md
index d6c6ec8..81517c5 100644
--- a/documentation/index.md
+++ b/documentation/index.md
@@ -1,17 +1,142 @@
-# Titanium Module for Google Cloud Messaging Push Notifications for Android #
+# Titanium Module for Firebase Cloud Messaging Push Notifications for Android #
[](http://gitt.io/component/nl.vanvianen.android.gcm)
-A Titanium module for registering a device with Google Cloud Messaging and handling push notifications sent to the device. Both push notifications and topic subscriptions are supported.
+A Titanium module for registering a device with Firebase Cloud Messaging and handling push notifications sent to the device. Both push notifications and topic subscriptions are supported.
-1. Install the module as usual in Appcelerator Studio by downloading the [zip file](https://github.com/morinel/gcmpush/releases/download/1.5/nl.vanvianen.android.gcm-android-1.5.zip) or use ```gittio install nl.vanvianen.android.gcm```
+1. Install the module as usual in Appcelerator Studio by downloading the zip file of the [latest release](https://github.com/morinel/gcmpush/releases/latest) or use `gittio install nl.vanvianen.android.gcm`
+1. Set up your [Firebase project](https://console.firebase.google.com/).
+1. Download the `google-services.json` file and put it in `PROJECT_FOLDER/app/assets/android/` (or `PROJECT_FOLDER/Resources/android/` for non-Alloy projects).
1. Refer to the examples for possibilities.
-1. Send a server push notification with your preferred server-side technology to the registrationId returned while registering your device.
+1. Send a server push notification with your preferred server-side technology to the registrationId returned from calling `registerPush()`.
1. The callback you specified will then be called.
This module does not require any tiapp.xml properties, all configuration is done in Javascript.
-## Example server-side code to send a push notification ##
+
+## Version 3 Ugrade Guide ##
+
+The hardest part about upgrading this module to v3 is the external configuration of Firebase. Most of your code should work the same, with one notable addition. You may need to add a `registration` callback in the `registerPush` parameters which handles updating your stored devide token (AKA registration ID) with whatever push notification service you are using. If you are using Firebase directly for sending push notifications, this won't be necessary.
+
+You can also remove the `senderId` option in the `registerPush` parameters, as that is handled with the `google-services.json` file.
+
+
+## Register your app for push notifications ##
+
+See [this example](https://github.com/morinel/gcmpush/blob/master/example/app.js).
+
+It is not required to define `firebaseFile` or `firebaseConfig` if you have put your `google-services.json` file in the correct place. However, these options are available for advanced configurations. If your json file is named differently or is located in a subfolder, you must pass the path to the `firebaseFile` option.
+
+Alternatively, you can copy the JSON out of the file and pass it directly with the `firebaseConfig` option. This is useful if you have different Firebase projects for dev and production—you can pass different JSON depending on the build type, and if you're using Alloy you can add it to your config.json file and pass `Alloy.CFG.firebase`.
+
+```
+var gcm = require("nl.vanvianen.android.gcm");
+
+gcm.registerPush({
+ /* Firebase config file (if you've renamed it or if it's in a subfolder) */
+ firebaseFile: 'somepath/google-services.json',
+ /* Firebase config JSON */
+ firebaseConfig: {
+ # ...
+ },
+ notificationSettings: {
+ sound: 'mysound.mp3', /* Place sound file in app/platform/android/res/raw/mysound.mp3 */
+ smallIcon: 'notification_icon.png', /* Place icon in app/platform/android/res/drawable/notification_icon.png */
+ largeIcon: 'appicon.png', /* Same */
+ vibrate: true, /* Whether the phone should vibrate */
+ insistent: true, /* Whether the notification should be insistent */
+ group: 'MyNotificationGroup', /* Name of group to group similar notifications together */
+ localOnly: false, /* Whether this notification should be bridged to other devices */
+ priority: +2, /* Notification priority, from -2 to +2 */
+ bigText: false,
+ /* You can also set a static value for title, message, or ticker. If you set a value here, the key will be ignored. */
+ // title: '',
+ // message: '',
+ // ticker: ''
+ /* Add LED flashing */
+ ledOn: 200,
+ ledOff: 300
+ /* Android O channels */
+ channelId: 'my_channel',
+ channelName: 'My Channel'
+ },
+ success: function (event) {
+ Ti.API.debug("Push registration success: " + JSON.stringify(event));
+
+ /* Add code to send event.registrationId to your server */
+ },
+ error: function (event) {
+ Ti.API.debug("Push registration error: " + JSON.stringify(event));
+ alert(event.error);
+ },
+ callback: function (event) {
+ Ti.API.debug("Push callback: " + JSON.stringify(event));
+ /* Called when a notification is received and the app is in the foreground */
+
+ var dialog = Ti.UI.createAlertDialog({
+ title: 'Push received',
+ message: JSON.stringify(event.data),
+ buttonNames: ['View','Cancel'],
+ cancel: 1
+ });
+ dialog.addEventListener("click", function(event) {
+ dialog.hide();
+ if (event.index == 0) {
+ /* Do stuff to view the notification */
+ }
+ });
+ dialog.show();
+ },
+ registration: function (event) {
+ Ti.API.debug("Registration callback: " + JSON.stringify(event));
+ /* Called when the registration token has changed */
+
+ /* Add code to send event.registrationId to your server */
+ }
+});
+```
+
+
+## Notification settings for push notifications ##
+
+See the [example](https://github.com/morinel/gcmpush/blob/master/example/app.js) for an overview of how to specify the below settings:
+
+1. **smallIcon**: the tiny icon shown at the top of the screen, see this [stackoverflow question](http://stackoverflow.com/questions/28387602/notification-bar-icon-turns-white-in-android-5-lollipop) for details. The file should be placed in the ```platform/android/res/drawable``` directory.
+1. **largeIcon**: the large icon shown in the notification bar. If not specified your appicon will be used. The file should be placed in the ```platform/android/res/drawable``` directory.
+1. **sound**: the sound file to play while receiving the notification or 'default' for the default sound. The sound file should be placed in the ```platform/android/res/raw``` directory.
+1. **vibrate** (true / false): whether vibration should be on, default false.
+1. **insistent** (true / false): whether the notification should be [insistent](http://developer.android.com/reference/android/app/Notification.html#FLAG_INSISTENT), default false.
+1. **group**: name of group to group similar notifications together, default null.
+1. **localOnly** (true / false): whether this notification should be bridged to other devices (false) or is only relevant to this device (true), default true.
+1. **backgroundOnly** (true / false): whether the app should only be notified when it's in the background, default false.
+1. **priority**: (integer) specifies the priority of the notification, should be between [PRIORITY_MIN](http://developer.android.com/reference/android/support/v4/app/NotificationCompat.html#PRIORITY_MIN) and [PRIORITY_MAX](http://developer.android.com/reference/android/support/v4/app/NotificationCompat.html#PRIORITY_MAX), default 0.
+1. **bigText** (true / false): whether this notification should use the [bigText style](http://developer.android.com/reference/android/app/Notification.BigTextStyle.html), default false.
+1. **titleKey** (string): specify a custom key name for the notification title sent by the server, default ```title```
+1. **messageKey** (string): specify a custom key name for the notification message sent by the server, default ```message```
+1. **tickerKey** (string): specify a custom key name for the notification ticker text sent by the server, default ```ticker```
+1. **title** (string): specify a static title for the notification (server data will be ignored)
+1. **message** (string): specify a static message for the notification (server data will be ignored)
+1. **ticker** (string): specify a static ticker for the notification (server data will be ignored)
+1. **ledOn** (integer): the number of ms the LED should be on while flashing, see [javadoc](http://developer.android.com/reference/android/app/Notification.html#ledOnMS)
+1. **ledOff** (integer): the number of ms the LED should be off while flashing, see [javadoc](http://developer.android.com/reference/android/app/Notification.html#ledOffMS)
+1. **notificationId** (integer): a (unique) integer to identify the notification. If specified, subsequent notifications will not override the previous one.
+1. **channelId** (string): [Android O] a unique identifier for the channel the notification belongs to, should be lower-case with underscores
+1. **channelName** (string) [Android O] a human-readable name for the notification channel
+
+
+The settings sound, vibrate, insistent, group, localOnly, priority, bigText and notificationId can also be set as data in the push message being received (see the server-side example above).
+
+If the app is not active when the notification is received, use gcm.getLastData() to retrieve the contents of the notification and act accordingly to start or resume the app in a suitable way. If you're done, call gcm.clearLastData(), otherwise the same logic will happen when resuming the app again, see the [example](https://github.com/morinel/gcmpush/blob/master/example/app.js).
+
+
+## Testing notifications in your app ##
+
+There currently seems to be an issue which causes the registration token to be invalidated when rebuilding the app after having already built & installed. If you are not receiving notifications during development, you may need to uninstall the app from your device and rebuild/reinstall.
+
+
+## Example server-side Java code to send a push notification ##
+
+**THIS EXAMPLE NO LONGER APPLIES WITH FCM**
Use the following dependency:
@@ -59,44 +184,10 @@ public void sendPush() {
```
-## Register your app for push notifications ##
-
-See [this example](https://github.com/morinel/gcmpush/blob/master/example/app.js).
-
-
-## Notification settings for push notifications ##
-
-See the [example](https://github.com/morinel/gcmpush/blob/master/example/app.js) for an overview of how to specify the below settings:
-
-1. **smallIcon**: the tiny icon shown at the top of the screen, see this [stackoverflow question](http://stackoverflow.com/questions/28387602/notification-bar-icon-turns-white-in-android-5-lollipop) for details. The file should be placed in the ```platform/android/res/drawable``` directory.
-1. **largeIcon**: the large icon shown in the notification bar. If not specified your appicon will be used. The file should be placed in the ```platform/android/res/drawable``` directory.
-1. **sound**: the sound file to play while receiving the notification or 'default' for the default sound. The sound file should be placed in the ```platform/android/res/raw``` directory.
-1. **vibrate** (true / false): whether vibration should be on, default false.
-1. **insistent** (true / false): whether the notification should be [insistent](http://developer.android.com/reference/android/app/Notification.html#FLAG_INSISTENT), default false.
-1. **group**: name of group to group similar notifications together, default null.
-1. **localOnly** (true / false): whether this notification should be bridged to other devices (false) or is only relevant to this device (true), default true.
-1. **backgroundOnly** (true / false): whether the app should only be notified when it's in the background, default false.
-1. **priority**: (integer) specifies the priority of the notification, should be between [PRIORITY_MIN](http://developer.android.com/reference/android/support/v4/app/NotificationCompat.html#PRIORITY_MIN) and [PRIORITY_MAX](http://developer.android.com/reference/android/support/v4/app/NotificationCompat.html#PRIORITY_MAX), default 0.
-1. **bigText** (true / false): whether this notification should use the [bigText style](http://developer.android.com/reference/android/app/Notification.BigTextStyle.html), default false.
-1. **titleKey** (string): specify a custom key name for the notification title sent by the server, default ```title```
-1. **messageKey** (string): specify a custom key name for the notification message sent by the server, default ```message```
-1. **tickerKey** (string): specify a custom key name for the notification ticker text sent by the server, default ```ticker```
-1. **title** (string): specify a static title for the notification (server data will be ignored)
-1. **message** (string): specify a static message for the notification (server data will be ignored)
-1. **ticker** (string): specify a static ticker for the notification (server data will be ignored)
-1. **ledOn** (integer): the number of ms the LED should be on while flashing, see [javadoc](http://developer.android.com/reference/android/app/Notification.html#ledOnMS)
-1. **ledOff** (integer): the number of ms the LED should be off while flashing, see [javadoc](http://developer.android.com/reference/android/app/Notification.html#ledOffMS)
-1. **notificationId** (integer): a (unique) integer to identify the notification. If specified, subsequent notifications will not override the previous one.
-
-
-The settings sound, vibrate, insistent, group, localOnly, priority, bigText and notificationId can also be set as data in the push message being received (see the server-side example above).
-
-If the app is not active when the notification is received, use gcm.getLastData() to retrieve the contents of the notification and act accordingly to start or resume the app in a suitable way. If you're done, call gcm.clearLastData(), otherwise the same logic will happen when resuming the app again, see the [example](https://github.com/morinel/gcmpush/blob/master/example/app.js).
-
-
-
## Example server-side code to send message to a topic ##
+**THIS EXAMPLE NO LONGER APPLIES WITH FCM**
+
```java
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
diff --git a/example/app.js b/example/app.js
index 6ee1f03..8bb1a98 100644
--- a/example/app.js
+++ b/example/app.js
@@ -8,12 +8,9 @@ if (lastData) {
}
gcm.registerPush({
- /* The Sender ID from Google Developers Console, see https://console.developers.google.com/project/XXXXXXXX/apiui/credential */
- /* It's the same as your project id */
- senderId: 'XXXXXXXX',
notificationSettings: {
- sound: 'mysound.mp3', /* Place sound file in platform/android/res/raw/mysound.mp3 */
- smallIcon: 'notification_icon.png', /* Place icon in platform/android/res/drawable/notification_icon.png */
+ sound: 'mysound.mp3', /* Place sound file in app/platform/android/res/raw/mysound.mp3 */
+ smallIcon: 'notification_icon.png', /* Place icon in app/platform/android/res/drawable/notification_icon.png */
largeIcon: 'appicon.png', /* Same */
vibrate: true, /* Whether the phone should vibrate */
insistent: true, /* Whether the notification should be insistent */
@@ -28,17 +25,21 @@ gcm.registerPush({
/* Add LED flashing */
ledOn: 200,
ledOff: 300
+ /* Android O channels */
+ channelId: 'my_channel',
+ channelName: 'My Channel'
},
success: function (event) {
- Ti.API.info("Push registration success: " + JSON.stringify(event));
+ Ti.API.debug("Push registration success: " + JSON.stringify(event));
+
/* Add code to send event.registrationId to your server */
},
error: function (event) {
- Ti.API.info("Push registration error: " + JSON.stringify(event));
+ Ti.API.debug("Push registration error: " + JSON.stringify(event));
alert(event.error);
},
callback: function (event) {
- Ti.API.info("Push callback = " + JSON.stringify(event));
+ Ti.API.debug("Push callback: " + JSON.stringify(event));
/* Called when a notification is received and the app is in the foreground */
var dialog = Ti.UI.createAlertDialog({
@@ -53,6 +54,12 @@ gcm.registerPush({
/* Do stuff to view the notification */
}
});
- dialog.show();
+ dialog.show();
+ },
+ registration: function (event) {
+ Ti.API.debug("Registration callback: " + JSON.stringify(event));
+ /* Called when the registration token has changed */
+
+ /* Add code to send event.registrationId to your server */
}
});
diff --git a/java-sources.txt b/java-sources.txt
index 6cdb4e9..f96b308 100644
--- a/java-sources.txt
+++ b/java-sources.txt
@@ -1,12 +1,12 @@
-"/Users/jeroen/Projects/gcmpush/src/nl/vanvianen/android/gcm/AppStateListener.java"
-"/Users/jeroen/Projects/gcmpush/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java"
-"/Users/jeroen/Projects/gcmpush/src/nl/vanvianen/android/gcm/GCMIntentService.java"
-"/Users/jeroen/Projects/gcmpush/src/nl/vanvianen/android/gcm/GCMModule.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/java/nl/vanvianen/android/gcm/GcmBootstrap.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/android/support/compat/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/android/support/design/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/android/support/v7/appcompat/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/android/support/v7/cardview/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/com/google/android/gms/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/nl/vanvianen/android/gcm/R.java"
-"/Users/jeroen/Projects/gcmpush/build/generated/r/ti/modules/titanium/ui/R.java"
\ No newline at end of file
+"/Users/coryhughart/Dev/github/gcmpush/src/nl/vanvianen/android/gcm/AppStateListener.java"
+"/Users/coryhughart/Dev/github/gcmpush/src/nl/vanvianen/android/gcm/FCMService.java"
+"/Users/coryhughart/Dev/github/gcmpush/src/nl/vanvianen/android/gcm/GCMModule.java"
+"/Users/coryhughart/Dev/github/gcmpush/src/nl/vanvianen/android/gcm/IIDService.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/java/nl/vanvianen/android/gcm/GcmBootstrap.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/android/support/compat/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/android/support/design/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/android/support/v7/appcompat/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/android/support/v7/cardview/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/com/google/android/gms/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/nl/vanvianen/android/gcm/R.java"
+"/Users/coryhughart/Dev/github/gcmpush/build/generated/r/ti/modules/titanium/ui/R.java"
\ No newline at end of file
diff --git a/lib/firebase-common-11.0.4.aar b/lib/firebase-common-11.0.4.aar
new file mode 100644
index 0000000..e368862
Binary files /dev/null and b/lib/firebase-common-11.0.4.aar differ
diff --git a/lib/firebase-iid-11.0.4.aar b/lib/firebase-iid-11.0.4.aar
new file mode 100644
index 0000000..4b218ec
Binary files /dev/null and b/lib/firebase-iid-11.0.4.aar differ
diff --git a/lib/firebase-messaging-11.0.4.aar b/lib/firebase-messaging-11.0.4.aar
new file mode 100644
index 0000000..220113a
Binary files /dev/null and b/lib/firebase-messaging-11.0.4.aar differ
diff --git a/lib/gcm-server.jar b/lib/gcm-server.jar
deleted file mode 100644
index ac1dc63..0000000
Binary files a/lib/gcm-server.jar and /dev/null differ
diff --git a/lib/gson-2.3.1.jar b/lib/gson-2.3.1.jar
deleted file mode 100644
index 250132c..0000000
Binary files a/lib/gson-2.3.1.jar and /dev/null differ
diff --git a/lib/gson-2.8.5.jar b/lib/gson-2.8.5.jar
new file mode 100644
index 0000000..0d5baf3
Binary files /dev/null and b/lib/gson-2.8.5.jar differ
diff --git a/manifest b/manifest
index 90358c4..e360430 100755
--- a/manifest
+++ b/manifest
@@ -2,9 +2,9 @@
# this is your module manifest and used by Titanium
# during compilation, packaging, distribution, etc.
#
-version: 2.0
+version: 3.0
apiversion: 4
-description: Google Cloud Push for Titanium
+description: Firebase Cloud Messaging for Titanium
author: Jeroen van Vianen
license: Apache License, Version 2.0
copyright: Copyright (c) 2015-2017 by Jeroen van Vianen
diff --git a/platform/android/res/values/values.xml b/platform/android/res/values/values.xml
new file mode 100644
index 0000000..ffa43a7
--- /dev/null
+++ b/platform/android/res/values/values.xml
@@ -0,0 +1,6 @@
+
+
+
+
+Miscellaneous
+
\ No newline at end of file
diff --git a/src/nl/vanvianen/android/gcm/GCMIntentService.java b/src/nl/vanvianen/android/gcm/FCMService.java
similarity index 85%
rename from src/nl/vanvianen/android/gcm/GCMIntentService.java
rename to src/nl/vanvianen/android/gcm/FCMService.java
index 87082ea..953427d 100755
--- a/src/nl/vanvianen/android/gcm/GCMIntentService.java
+++ b/src/nl/vanvianen/android/gcm/FCMService.java
@@ -18,14 +18,18 @@
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
-import com.google.android.gcm.GCMBaseIntentService;
+import android.os.Build;
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
import com.google.gson.Gson;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.TiApplication;
@@ -38,9 +42,9 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
-public class GCMIntentService extends GCMBaseIntentService {
+public class FCMService extends FirebaseMessagingService {
- private static final String LCAT = "GCMIntentService";
+ private static final String LCAT = "FCMService";
private static final String UNREGISTER_EVENT = "unregister";
@@ -50,22 +54,8 @@ public class GCMIntentService extends GCMBaseIntentService {
private final static AtomicInteger notificationCounter = new AtomicInteger(0);
- public GCMIntentService() {
- super("");
- }
-
- @Override
- public void onRegistered(Context context, String registrationId) {
- Log.d(LCAT, "Registered: " + registrationId);
-
- GCMModule.getInstance().sendSuccess(registrationId);
- }
-
- @Override
- public void onUnregistered(Context context, String registrationId) {
- Log.d(LCAT, "Unregistered");
-
- GCMModule.getInstance().fireEvent(UNREGISTER_EVENT, new HashMap());
+ public FCMService() {
+ super();
}
private int getResource(String type, String name) {
@@ -88,14 +78,22 @@ private int getResource(String type, String name) {
@Override
@SuppressWarnings("unchecked")
- protected void onMessage(Context context, Intent intent) {
- Log.d(LCAT, "Push notification received");
+ public void onMessageReceived(RemoteMessage remoteMessage) {
+ //Log.d(LCAT, "Push notification received");
+ Log.d(LCAT, "Push notification received from: "+remoteMessage.getFrom()); // AbstractSafeParcelable
+
+ if (remoteMessage.getData() == null) {
+ Log.w(LCAT, "No notification data received. Aborting.");
+ return;
+ }
+
+ Map params = remoteMessage.getData();
boolean isTopic = false;
HashMap data = new HashMap();
- for (String key : intent.getExtras().keySet()) {
- Object value = intent.getExtras().get(key);
+ for (String key : params.keySet()) {
+ Object value = params.get(key);
Log.d(LCAT, "Message key: \"" + key + "\" value: \"" + value + "\"");
if (key.equals("from") && value instanceof String && ((String) value).startsWith("/topics/")) {
@@ -103,7 +101,7 @@ protected void onMessage(Context context, Intent intent) {
}
String eventKey = key.startsWith("data.") ? key.substring(5) : key;
- data.put(eventKey, intent.getExtras().get(key));
+ data.put(eventKey, params.get(key));
if (value instanceof String && ((String) value).startsWith("{")) {
Log.d(LCAT, "Parsing JSON string...");
@@ -140,7 +138,7 @@ protected void onMessage(Context context, Intent intent) {
int priority = 0;
boolean bigText = false;
int notificationId = 1;
-
+
Integer ledOn = null;
Integer ledOff = null;
@@ -153,6 +151,9 @@ protected void onMessage(Context context, Intent intent) {
boolean backgroundOnly = false;
+ String channelId = null;
+ String channelName = null;
+
Map notificationSettings = new Gson().fromJson(TiApplication.getInstance().getAppProperties().getString(GCMModule.NOTIFICATION_SETTINGS, null), Map.class);
if (notificationSettings != null) {
if (notificationSettings.get("smallIcon") instanceof String) {
@@ -313,6 +314,23 @@ protected void onMessage(Context context, Intent intent) {
}
}
+ if (notificationSettings.get("channelId") != null) {
+ if (notificationSettings.get("channelId") instanceof String) {
+ channelId = (String) notificationSettings.get("channelId");
+ channelId = channelId.replaceAll(" ", "_").toLowerCase();
+ } else {
+ Log.e(LCAT, "Invalid setting channelId, should be a String");
+ }
+ }
+
+ if (notificationSettings.get("channelName") != null) {
+ if (notificationSettings.get("channelName") instanceof String) {
+ channelName = (String) notificationSettings.get("channelName");
+ } else {
+ Log.e(LCAT, "Invalid setting channelName, should be a String");
+ }
+ }
+
} else {
Log.d(LCAT, "No notification settings found");
}
@@ -373,7 +391,7 @@ protected void onMessage(Context context, Intent intent) {
Log.d(LCAT, "No large icon found");
}
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(TiApplication.getInstance().getApplicationContext())
.setContentTitle(title)
.setContentText(message)
.setTicker(ticker)
@@ -417,14 +435,35 @@ protected void onMessage(Context context, Intent intent) {
}
Log.i(LCAT, "bigText: " + bigText);
- Notification notification = builder.build();
-
/* Sound, can also be set in the push notification payload */
if (data.get("sound") != null) {
Log.d(LCAT, "Sound specified in notification");
sound = (String) data.get("sound");
}
+ NotificationManager notificationManager =
+ (NotificationManager) TiApplication.getInstance().getApplicationContext().getSystemService(TiApplication.NOTIFICATION_SERVICE);
+
+ // Since android Oreo notification channel is needed.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Uri soundUri = Uri.parse("android.resource://" + pkg + "/" + getResource("raw", sound));
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build();
+
+ if (channelId != null && channelName != null) {
+ NotificationChannel channel = new NotificationChannel(channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(soundUri, attributes);
+ notificationManager.createNotificationChannel(channel);
+ builder.setChannelId(channelId);
+ }
+ Log.i(LCAT, "channelId: " + channelId);
+ }
+
+ Notification notification = builder.build();
+
if ("default".equals(sound)) {
Log.i(LCAT, "Sound: default sound");
notification.defaults |= Notification.DEFAULT_SOUND;
@@ -442,6 +481,7 @@ protected void onMessage(Context context, Intent intent) {
}
Log.i(LCAT, "Vibrate: " + vibrate);
+
/* Insistent, can also be set in the push notification payload */
if ("true".equals(data.get("insistent"))) {
insistent = true;
@@ -482,10 +522,18 @@ protected void onMessage(Context context, Intent intent) {
notification.flags |= Notification.FLAG_AUTO_CANCEL;
- ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(notificationId, notification);
+ notificationManager.notify(notificationId, notification);
}
}
+ /*
+ @Override
+ protected void onDeletedMessages() {
+ // TODO: https://firebase.google.com/docs/cloud-messaging/android/receive#override-ondeletedmessages
+ }
+ */
+
+ /*
@Override
public void onError(Context context, String errorId) {
Log.e(LCAT, "Error: " + errorId);
@@ -505,4 +553,5 @@ public boolean onRecoverableError(Context context, String errorId) {
return true;
}
+ */
}
diff --git a/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java b/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java
deleted file mode 100644
index c63545f..0000000
--- a/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Copyright 2015 Jeroen van Vianen
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package nl.vanvianen.android.gcm;
-
-import android.content.Context;
-
-public class GCMBroadcastReceiver extends com.google.android.gcm.GCMBroadcastReceiver {
- @Override
- protected String getGCMIntentServiceClassName(Context context) {
- return GCMIntentService.class.getName();
- }
-}
diff --git a/src/nl/vanvianen/android/gcm/GCMModule.java b/src/nl/vanvianen/android/gcm/GCMModule.java
index d5c24b9..043c72a 100644
--- a/src/nl/vanvianen/android/gcm/GCMModule.java
+++ b/src/nl/vanvianen/android/gcm/GCMModule.java
@@ -19,10 +19,17 @@
import android.app.Activity;
import android.app.NotificationManager;
import android.os.AsyncTask;
-import com.google.android.gcm.GCMRegistrar;
-import com.google.android.gms.gcm.GcmPubSub;
-import com.google.android.gms.gcm.GoogleCloudMessaging;
-import com.google.android.gms.iid.InstanceID;
+import android.os.Bundle;
+//import android.support.annotation.NonNull;
+import android.support.v4.content.LocalBroadcastManager;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+//import com.google.android.gms.tasks.OnCompleteListener;
+//import com.google.android.gms.tasks.Task;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.iid.FirebaseInstanceId;
+import com.google.firebase.messaging.FirebaseMessaging;
import com.google.gson.Gson;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollFunction;
@@ -30,8 +37,13 @@
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.TiApplication;
-import org.json.JSONObject;
+import org.appcelerator.titanium.io.TiFileFactory;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@@ -39,6 +51,7 @@
public class GCMModule extends KrollModule {
// Standard Debugging variables
private static final String LCAT = "GCMModule";
+ private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private static GCMModule instance = null;
private static AppStateListener appStateListener = null;
@@ -47,12 +60,15 @@ public class GCMModule extends KrollModule {
private KrollFunction successCallback = null;
private KrollFunction errorCallback = null;
private KrollFunction messageCallback = null;
+ private KrollFunction tokenCallback = null;
/* Callbacks for topics */
private KrollFunction successTopicCallback = null;
private KrollFunction errorTopicCallback = null;
private KrollFunction topicCallback = null;
+ private String jsonFile = "google-services.json";
+ private Boolean waitingForToken = false;
public static final String LAST_DATA = "nl.vanvianen.android.gcm.last_data";
public static final String NOTIFICATION_SETTINGS = "nl.vanvianen.android.gcm.notification_settings";
@@ -64,49 +80,99 @@ public GCMModule() {
appStateListener = new AppStateListener();
TiApplication.addActivityTransitionListener(appStateListener);
}
-
}
public boolean isInForeground() {
return AppStateListener.oneActivityIsResumed;
}
+ public String getToken(){
+ // get token and return it
+ try {
+ return FirebaseInstanceId.getInstance().getToken();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ public void tokenRefresh(String token) {
+ if (token != null) {
+ if (waitingForToken) {
+ sendSuccess(token);
+ waitingForToken = false;
+ }
+ sendTokenUpdate(token);
+ }
+ }
+
@Kroll.method
@SuppressWarnings("unchecked")
public void registerPush(HashMap options) {
Log.d(LCAT, "registerPush called");
- String senderId = (String) options.get("senderId");
Map notificationSettings = (Map) options.get("notificationSettings");
- successCallback = (KrollFunction) options.get("success");
- errorCallback = (KrollFunction) options.get("error");
+
+ // Required callback
messageCallback = (KrollFunction) options.get("callback");
+ // Optional callbacks
+ successCallback = options.containsKey("success") ? (KrollFunction)options.get("success") : null;
+ errorCallback = options.containsKey("error") ? (KrollFunction)options.get("error") : null;
+ tokenCallback = options.containsKey("registration") ? (KrollFunction)options.get("registration") : null;
+
/* Store notification settings in global Ti.App properties */
- JSONObject json = new JSONObject(notificationSettings);
- TiApplication.getInstance().getAppProperties().setString(GCMModule.NOTIFICATION_SETTINGS, json.toString());
+ JSONObject notificationJSON = new JSONObject(notificationSettings);
+ TiApplication.getInstance().getAppProperties().setString(GCMModule.NOTIFICATION_SETTINGS, notificationJSON.toString());
- if (senderId != null) {
- GCMRegistrar.register(TiApplication.getInstance(), senderId);
+ parseBootIntent();
+ if (!checkPlayServices()) {
+ Log.e(LCAT, "Google Play Services are not available on this device");
+ return;
+ }
+
+ Map config = null;
+ // if "FirebaseConfig" param exists, "firebaseFile" is ignored
+ if (options.containsKey("firebaseConfig")) {
+ config = (Map) options.get("firebaseConfig");
+ }
+ else if (options.containsKey("firebaseFile")) {
+ config = new HashMap();
+ config.put("file", (String) options.get("firebaseFile"));
+ }
+
+ if (initializeFirebase(config)) {
String registrationId = getRegistrationId();
if (registrationId != null && registrationId.length() > 0) {
sendSuccess(registrationId);
}
- } else {
- sendError(errorCallback, "No GCM senderId specified; get it from the Google Play Developer Console");
+ else {
+ //sendError(errorCallback, "Registration ID is not (yet) available. The `registration` callback will provide the registration ID when it is available.");
+ waitingForToken = true;
+ }
+ }
+ else {
+ sendError(errorCallback, "Could not initialize Firebase");
}
}
@Kroll.method
public void unregister() {
Log.d(LCAT, "unregister called (" + (instance != null) + ")");
- try {
- GCMRegistrar.unregister(TiApplication.getInstance());
- } catch (Exception ex) {
- Log.e(LCAT, "Cannot unregister from push: " + ex.getMessage());
- }
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FirebaseInstanceId.getInstance().deleteInstanceId();
+ Log.d(LCAT, "Delete instanceid succeeded");
+ } catch (Exception ex) {
+ Log.e(LCAT, "Remove token failed - error: " + ex.getMessage());
+ }
+ return null;
+ }
+ }.execute();
}
@@ -114,7 +180,7 @@ public void unregister() {
@Kroll.getProperty
public String getRegistrationId() {
Log.d(LCAT, "get registrationId property");
- return GCMRegistrar.getRegistrationId(TiApplication.getInstance());
+ return getToken();
}
@@ -123,8 +189,7 @@ public void subscribe(final HashMap options) {
Log.d(LCAT, "subscribe called");
// subscripe to a topic
- final String senderId = (String) options.get("senderId");
- final String topic = (String) options.get("topic");
+ String _topic = (String) options.get("topic");
if (options.get("success") != null) {
successTopicCallback = (KrollFunction) options.get("success");
@@ -136,93 +201,158 @@ public void subscribe(final HashMap options) {
topicCallback = (KrollFunction) options.get("callback");
}
- if (topic == null || !topic.startsWith("/topics/")) {
- sendError(errorTopicCallback, "No or invalid topic specified, should start with /topics/");
+ if (_topic == null) {
+ Log.e(LCAT, "No or invalid topic specified");
}
- if (senderId != null) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- String token = getToken(senderId);
- GcmPubSub.getInstance(TiApplication.getInstance()).subscribe(token, topic, null);
-
- if (successTopicCallback != null) {
- // send success callback
- HashMap data = new HashMap();
- data.put("success", true);
- data.put("topic", topic);
- successTopicCallback.callAsync(getKrollObject(), data);
- }
- } catch (Exception ex) {
- // error
- Log.e(LCAT, "Error " + ex.toString());
- if (errorTopicCallback != null) {
- // send error callback
- HashMap data = new HashMap();
- data.put("success", false);
- data.put("topic", topic);
- data.put("error", ex.toString());
- errorCallback.callAsync(getKrollObject(), data);
- }
+ if (_topic.startsWith("/topics/")) {
+ Log.w(LCAT, "Topic should NOT start with '/topic/'. Please update your implementation.");
+ _topic = _topic.substring(8);
+ }
+
+ final String topic = _topic;
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FirebaseMessaging.getInstance().subscribeToTopic(topic);
+
+ if (successTopicCallback != null) {
+ // send success callback
+ HashMap data = new HashMap();
+ data.put("success", true);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ successTopicCallback.callAsync(getKrollObject(), data);
+ }
+ } catch (Exception ex) {
+ // error
+ Log.e(LCAT, "Subscribe error " + ex.toString());
+ if (errorTopicCallback != null) {
+ // send error callback
+ HashMap data = new HashMap();
+ data.put("success", false);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ data.put("error", ex.toString());
+ errorCallback.callAsync(getKrollObject(), data);
}
- return null;
}
- }.execute();
- } else {
- sendError(errorTopicCallback, "No GCM senderId specified; get it from the Google Play Developer Console");
- }
+ return null;
+ }
+ }.execute();
+
+ /*
+ // Requires Firebase Messaging 17.0.0+ & Play Services 15.0.1+
+ FirebaseMessaging.getInstance().subscribeToTopic(topic).addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (!task.isSuccessful()) {
+ if (errorTopicCallback != null) {
+ // send error callback
+ HashMap data = new HashMap();
+ data.put("success", false);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ data.put("error", "Cannot subscribe to topic "+topic);
+ errorCallback.callAsync(getKrollObject(), data);
+ }
+ return;
+ }
+
+ if (successTopicCallback != null) {
+ // send success callback
+ HashMap data = new HashMap();
+ data.put("success", true);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ successTopicCallback.callAsync(getKrollObject(), data);
+ }
+ }
+ });
+ */
}
@Kroll.method
public void unsubscribe(final HashMap options) {
+ Log.d(LCAT, "unsubscribe called");
+
// unsubscripe from a topic
- final String senderId = (String) options.get("senderId");
- final String topic = (String) options.get("topic");
+ String _topic = (String) options.get("topic");
final KrollFunction callback = (KrollFunction) options.get("callback");
- if (topic == null || !topic.startsWith("/topics/")) {
- Log.e(LCAT, "No or invalid topic specified, should start with /topics/");
+ if (_topic == null) {
+ Log.e(LCAT, "No or invalid topic specified");
}
- if (senderId != null) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- String token = getToken(senderId);
- if (token != null) {
- GcmPubSub.getInstance(TiApplication.getInstance()).unsubscribe(token, topic);
-
- if (callback != null) {
- // send success callback
- HashMap data = new HashMap();
- data.put("success", true);
- data.put("topic", topic);
- data.put("token", token);
- callback.callAsync(getKrollObject(), data);
- }
- } else {
- sendError(callback, "Cannot unsubscribe from topic " + topic);
- }
- } catch (Exception ex) {
- sendError(callback, "Cannot unsubscribe from topic " + topic + ": " + ex.getMessage());
+ if (_topic.startsWith("/topics/")) {
+ Log.w(LCAT, "Topic should NOT start with '/topic/'. Please update your implementation.");
+ _topic = _topic.substring(8);
+ }
+
+ final String topic = _topic;
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
+
+ if (successTopicCallback != null) {
+ // send success callback
+ HashMap data = new HashMap();
+ data.put("success", true);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ successTopicCallback.callAsync(getKrollObject(), data);
+ }
+ } catch (Exception ex) {
+ // error
+ Log.e(LCAT, "Unsubscribe error " + ex.toString());
+ if (errorTopicCallback != null) {
+ // send error callback
+ HashMap data = new HashMap();
+ data.put("success", false);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ data.put("error", ex.toString());
+ errorCallback.callAsync(getKrollObject(), data);
}
- return null;
}
- }.execute();
- }
- }
+ return null;
+ }
+ }.execute();
+
+ /*
+ // Requires Firebase Messaging 17.0.0+ & Play Services 15.0.1+
+ FirebaseMessaging.getInstance().unsubscribeFromTopic(topic).addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (!task.isSuccessful()) {
+ if (errorTopicCallback != null) {
+ // send error callback
+ HashMap data = new HashMap();
+ data.put("success", false);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ data.put("error", "Cannot unsubscribe from topic "+topic);
+ errorCallback.callAsync(getKrollObject(), data);
+ }
+ return;
+ }
- public String getToken(String senderId){
- // get token and return it
- try {
- InstanceID instanceID = InstanceID.getInstance(TiApplication.getInstance());
- return instanceID.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
- } catch (Exception ex) {
- return null;
- }
+ if (successTopicCallback != null) {
+ // send success callback
+ HashMap data = new HashMap();
+ data.put("success", true);
+ data.put("topic", topic);
+ data.put("token", getToken());
+ successTopicCallback.callAsync(getKrollObject(), data);
+ }
+ }
+ });
+ */
}
@Kroll.method
@@ -273,6 +403,7 @@ public void setNotificationSettings(Map notificationSettings) {
public void sendSuccess(String registrationId) {
if (successCallback != null) {
+ Log.d(LCAT, "Sending success: " + registrationId);
HashMap data = new HashMap();
data.put("success", true);
data.put("registrationId", registrationId);
@@ -286,7 +417,7 @@ public void sendError(String error) {
public void sendError(KrollFunction callback, String error) {
- Log.e(LCAT, error);
+ Log.e(LCAT, "Sending error: " + error);
if (callback != null) {
HashMap data = new HashMap();
data.put("success", false);
@@ -296,8 +427,10 @@ public void sendError(KrollFunction callback, String error) {
}
}
- public void sendMessage(HashMap messageData) {
+ public void sendMessage(Map messageData) {
if (messageCallback != null) {
+ Log.d(LCAT, "Sending message");
+
HashMap data = new HashMap();
data.put("data", messageData);
data.put("inBackground", !isInForeground());
@@ -308,8 +441,10 @@ public void sendMessage(HashMap messageData) {
}
}
- public void sendTopicMessage(HashMap messageData) {
+ public void sendTopicMessage(Map messageData) {
if (topicCallback != null) {
+ Log.d(LCAT, "Sending topic message");
+
HashMap data = new HashMap();
data.put("data", messageData);
data.put("inBackground", !isInForeground());
@@ -320,6 +455,17 @@ public void sendTopicMessage(HashMap messageData) {
}
}
+ public void sendTokenUpdate(String token) {
+ if (tokenCallback != null) {
+ Log.d(LCAT, "Sending token update: " + token);
+
+ HashMap data = new HashMap();
+ data.put("success", true);
+ data.put("registrationId", token);
+ tokenCallback.callAsync(getKrollObject(), data);
+ }
+ }
+
@Kroll.onAppCreate
public static void onAppCreate(TiApplication app) {
Log.d(LCAT, "onAppCreate " + app + " (" + (instance != null) + ")");
@@ -328,12 +474,18 @@ public static void onAppCreate(TiApplication app) {
@Override
protected void initActivity(Activity activity) {
Log.d(LCAT, "initActivity " + activity + " (" + (instance != null) + ")");
+
+ checkPlayServices();
+
super.initActivity(activity);
}
@Override
public void onResume(Activity activity) {
Log.d(LCAT, "onResume " + activity + " (" + (instance != null) + ")");
+
+ checkPlayServices();
+
super.onResume(activity);
}
@@ -361,6 +513,196 @@ public void onStop(Activity activity) {
super.onStop(activity);
}
+ private void parseBootIntent() {
+ try {
+ Bundle extras = TiApplication.getAppRootOrCurrentActivity().getIntent().getExtras();
+ String notification = "";
+
+ if (extras != null) {
+ notification = extras.getString("data");
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ Log.d(LCAT, "Key: " + key + " Value: " + value);
+ }
+ }
+
+ if (notification != null && !notification.isEmpty()) {
+ /* Store data to be retrieved when resuming app as a JSON object, serialized as a String, otherwise
+ * Ti.App.Properties.getString(GCMModule.LAST_DATA) doesn't work. */
+ JSONObject json = new JSONObject(notification);
+ TiApplication.getInstance().getAppProperties().setString(GCMModule.LAST_DATA, json.toString());
+
+ Map notificationData = new Gson().fromJson(notification, Map.class);
+
+ sendMessage(notificationData);
+ } else {
+ Log.d(LCAT, "No notification in Intent");
+ }
+ } catch (Exception ex) {
+ Log.e(LCAT, "parseBootIntent" + ex);
+ }
+ }
+
+ /**
+ * 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() {
+ Activity activity = TiApplication.getAppRootOrCurrentActivity();
+ GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
+
+ int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity);
+ if (resultCode != ConnectionResult.SUCCESS) {
+ if (apiAvailability.isUserResolvableError(resultCode)) {
+ apiAvailability.getErrorDialog(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).show();
+ } else {
+ Log.w(LCAT, "This device is not supported.");
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Initializes Firebase manually since we do not have control over the
+ * Titanium-generated gradle file, so we must pass in the
+ * google-services.json info manually
+ *
+ * Thanks to @hansemannn
+ * https://github.com/hansemannn/titanium-firebase-core/blob/971bcd5c9b177e995c72320e6be618afc9df549e/android/src/firebase/core/TitaniumFirebaseCoreModule.java
+ *
+ * TODO: possibly allow Firebase config parameters from tiapp.xml?
+ * https://developers.google.com/android/guides/google-services-plugin#processing_the_json_file
+ */
+ private boolean initializeFirebase(Map config) {
+ // If config is empty, use default json file path
+ if (config == null) {
+ config = new HashMap();
+ config.put("file", jsonFile);
+ }
+
+ FirebaseOptions options = parseConfig(config);
+
+ try {
+ FirebaseApp.initializeApp(TiApplication.getInstance().getApplicationContext(), options);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.w(LCAT, "There was a problem initializing FirebaseApp or it was initialized a second time: " + e);
+ //return false;
+ }
+
+ Log.w(LCAT, "Loading Firebase without config (this probably won't work!)");
+ try {
+ FirebaseApp.initializeApp(TiApplication.getInstance().getApplicationContext());
+ return true;
+ } catch (IllegalStateException e) {
+ Log.w(LCAT, "There was a problem initializing FirebaseApp or it was initialized a second time: " + e);
+ return false;
+ }
+ }
+
+ /**
+ * Parse JSON into FirebaseOptions
+ */
+ private FirebaseOptions parseConfig(Map config) {
+ if (config == null) return null;
+
+ String apiKey = "";
+ String databaseURL = "";
+ String projectID = "";
+ String storageBucket = "";
+ String applicationID = "";
+ String GCMSenderID = "";
+ FirebaseOptions.Builder options = new FirebaseOptions.Builder();
+ JSONObject json = null;
+
+ // if "file" param exists, all other config params are ignored
+ if (config.containsKey("file")) {
+ String file = (String) config.get("file");
+ Log.d(LCAT, "Loading Firebase config from: " + file);
+ json = loadJSONFromAsset(file);
+ }
+ else {
+ Log.d(LCAT, "Loading Firebase config from params");
+ json = new JSONObject(config);
+ }
+
+ try {
+ JSONObject projectInfo = json.getJSONObject("project_info");
+ String packageName = TiApplication.getAppCurrentActivity().getPackageName();
+
+ if (projectInfo.has("storage_bucket")) {
+ storageBucket = projectInfo.getString("storage_bucket");
+ }
+ if (projectInfo.has("firebase_url")) {
+ databaseURL = projectInfo.getString("firebase_url");
+ }
+ if (projectInfo.has("project_number")) {
+ GCMSenderID = projectInfo.getString("project_number");
+ }
+ if (projectInfo.has("project_id")) {
+ projectID = projectInfo.getString("project_id");
+ }
+ if (json.has("client")) {
+ JSONArray clients = json.getJSONArray("client");
+ for (int i = 0, len = clients.length(); i < len; i++) {
+ JSONObject client = clients.getJSONObject(i);
+ JSONObject clientInfo = client.getJSONObject("client_info");
+ String pName = clientInfo.getJSONObject("android_client_info").getString("package_name");
+ if (pName.equals(packageName)) {
+ applicationID = client.getJSONObject("client_info").getString("mobilesdk_app_id");
+ apiKey = client.getJSONArray("api_key").getJSONObject(0).getString("current_key");
+ }
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(LCAT, "Error parsing JSON: " + e);
+ }
+
+ Log.d(LCAT, "apiKey: " + apiKey);
+ Log.d(LCAT, "databaseUrl: " + databaseURL);
+ Log.d(LCAT, "projectId: " + projectID);
+ Log.d(LCAT, "storageBucket: " + storageBucket);
+ Log.d(LCAT, "applicationId: " + applicationID);
+ Log.d(LCAT, "GcmSenderId: " + GCMSenderID);
+
+ options.setApiKey(apiKey);
+ options.setDatabaseUrl(databaseURL);
+ options.setProjectId(projectID);
+ options.setStorageBucket(storageBucket);
+ options.setApplicationId(applicationID);
+ options.setGcmSenderId(GCMSenderID);
+
+ return options.build();
+ }
+
+ private JSONObject loadJSONFromAsset(String filename) {
+ String json = null;
+
+ try {
+ String url = this.resolveUrl(null, filename);
+ Log.d(LCAT, "JSON Path: " + url);
+
+ InputStream inStream = TiFileFactory.createTitaniumFile(url, false).getInputStream();
+ byte[] buffer = new byte[inStream.available()];
+ inStream.read(buffer);
+ inStream.close();
+ json = new String(buffer, "UTF-8");
+ } catch (IOException e) {
+ Log.e(LCAT, "Error reading file: " + e);
+ return null;
+ }
+
+ try {
+ return new JSONObject(json);
+ } catch (JSONException e) {
+ Log.e(LCAT, "Error parsing JSON: " + e);
+ }
+
+ return null;
+ }
+
public static GCMModule getInstance() {
return instance;
}
diff --git a/src/nl/vanvianen/android/gcm/IIDService.java b/src/nl/vanvianen/android/gcm/IIDService.java
new file mode 100644
index 0000000..e3b2872
--- /dev/null
+++ b/src/nl/vanvianen/android/gcm/IIDService.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2018 Cory Hughart
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nl.vanvianen.android.gcm;
+
+import android.content.Intent;
+import android.util.Log;
+import android.support.v4.content.LocalBroadcastManager;
+
+import com.google.firebase.iid.FirebaseInstanceId;
+import com.google.firebase.iid.FirebaseInstanceIdService;
+
+public class IIDService extends FirebaseInstanceIdService {
+ // Standard Debugging variables
+ private static final String LCAT = "IIDService";
+
+ /**
+ * Called if InstanceID token is updated. This may occur if the security of
+ * the previous token had been compromised. Note that this is called when the InstanceID token
+ * is initially generated so this is where you would retrieve the token.
+ */
+ @Override
+ public void onTokenRefresh() {
+ // Get updated InstanceID token.
+ String refreshedToken = FirebaseInstanceId.getInstance().getToken();
+ Log.d(LCAT, "Refreshed token: " + refreshedToken);
+
+ // If you want to send messages to this application instance or
+ // manage this apps subscriptions on the server side, send the
+ // Instance ID token to your app server.
+ GCMModule.getInstance().tokenRefresh(refreshedToken);
+ }
+}
diff --git a/timodule.xml b/timodule.xml
index 63690e9..ab8ae0d 100644
--- a/timodule.xml
+++ b/timodule.xml
@@ -1,42 +1,40 @@
-
+
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
+
-
-
+
+
+
-
-
+
-
+
+
+
+
-
-
+
+
+
+
+