From 32e4bc5a7996012dc98d0897b03be7729601584b Mon Sep 17 00:00:00 2001 From: Manojkumar Date: Sat, 15 Oct 2016 22:34:22 +0530 Subject: [PATCH 1/6] Added ADM support for Amazon Devices --- .classpath | 21 + .gitignore | 13 +- android/LICENSE | 202 +++++++ android/build.properties | 4 + android/build.xml | 56 ++ android/lib/amazon-device-messaging-1.0.1.jar | Bin 0 -> 4410 bytes {lib => android/lib}/gcm-server.jar | Bin .../lib}/google-play-services-gms.jar | Bin {lib => android/lib}/gson-2.3.1.jar | Bin manifest => android/manifest | 4 +- .../android/gcm/ADMMessageHandler.java | 65 +++ .../android/gcm/AppStateListener.java | 0 .../android/gcm/GCMBroadcastReceiver.java | 0 .../android/gcm/GCMIntentService.java | 76 +++ .../nl/vanvianen/android/gcm/GCMModule.java | 428 +++++++++++++++ .../android/gcm/NotificationBuilder.java | 511 ++++++++++++++++++ android/timodule.xml | 51 ++ build.properties | 4 - build.xml | 10 - documentation/index.md | 9 + hooks/README | 1 - hooks/add.py | 35 -- hooks/install.py | 19 - hooks/remove.py | 34 -- hooks/uninstall.py | 18 - platform/README | 3 - platform/android/assets/api_key.txt | 1 + plugins/gcmpush/hooks/gcmpush.js | 53 ++ plugins/gcmpush/package.json | 10 + .../android/gcm/GCMIntentService.java | 508 ----------------- src/nl/vanvianen/android/gcm/GCMModule.java | 367 ------------- timodule.xml | 43 -- 32 files changed, 1499 insertions(+), 1047 deletions(-) create mode 100644 .classpath create mode 100644 android/LICENSE create mode 100644 android/build.properties create mode 100644 android/build.xml create mode 100644 android/lib/amazon-device-messaging-1.0.1.jar rename {lib => android/lib}/gcm-server.jar (100%) rename {lib => android/lib}/google-play-services-gms.jar (100%) rename {lib => android/lib}/gson-2.3.1.jar (100%) rename manifest => android/manifest (88%) mode change 100755 => 100644 create mode 100644 android/src/nl/vanvianen/android/gcm/ADMMessageHandler.java rename {src => android/src}/nl/vanvianen/android/gcm/AppStateListener.java (100%) rename {src => android/src}/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java (100%) create mode 100755 android/src/nl/vanvianen/android/gcm/GCMIntentService.java create mode 100644 android/src/nl/vanvianen/android/gcm/GCMModule.java create mode 100644 android/src/nl/vanvianen/android/gcm/NotificationBuilder.java create mode 100644 android/timodule.xml delete mode 100644 build.properties delete mode 100644 build.xml delete mode 100644 hooks/README delete mode 100644 hooks/add.py delete mode 100644 hooks/install.py delete mode 100644 hooks/remove.py delete mode 100644 hooks/uninstall.py delete mode 100644 platform/README create mode 100644 platform/android/assets/api_key.txt create mode 100644 plugins/gcmpush/hooks/gcmpush.js create mode 100644 plugins/gcmpush/package.json delete mode 100755 src/nl/vanvianen/android/gcm/GCMIntentService.java delete mode 100644 src/nl/vanvianen/android/gcm/GCMModule.java delete mode 100644 timodule.xml diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..0f66b85 --- /dev/null +++ b/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 876179a..eb232e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ -build -dist +.DS_Store +.apt_generated +.project +.settings +java-sources.txt +tmp +node_modules libs -build.properties +bin +build +dist \ No newline at end of file diff --git a/android/LICENSE b/android/LICENSE new file mode 100644 index 0000000..7b3b6a0 --- /dev/null +++ b/android/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + 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. + diff --git a/android/build.properties b/android/build.properties new file mode 100644 index 0000000..126965f --- /dev/null +++ b/android/build.properties @@ -0,0 +1,4 @@ +titanium.platform=/Users/manojkumar/Library/Application Support/Titanium/mobilesdk/osx/5.1.2.GA/android +android.platform=/Users/manojkumar/Library/Android/sdk/platforms/android-23 +google.apis=/Applications/android-sdk/add-ons/addon-google_apis-google-23 +android.ndk=/Applications/android-ndk-r11b \ No newline at end of file diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 0000000..1634b7a --- /dev/null +++ b/android/build.xml @@ -0,0 +1,56 @@ + + + Ant build script for Titanium Android module gcmpush + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/lib/amazon-device-messaging-1.0.1.jar b/android/lib/amazon-device-messaging-1.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..0fcef1de234f604bd48e7c12d1d3be592acbb606 GIT binary patch literal 4410 zcmb7H2{@E%8y<}8OO}cpN>L$W&yr=xGBmP=k(d~31~a6PEk?>7qlE0)Y7jZL9L(4% z3R$vMmcdx2P?i(_IEivv&i~H!ecv_L_1yRKeb4f~&uySb17rjM=mCJ%YOFfoo5cVC z0-%~E;NuW|Eznvk0AR3bN)KqEnBut)7Sd1w08x~Sivrs+g@W}VTAIct;!v%Gm}2J! z33iP4wc)poKtT&^viu^}b1KHjaS~=XIZEGe6f>>1vXJ0((4xwJy?yYicU&891m`lI z{ed@2eEe8nopMZrX1=ugg<}K8sSfQ_VLu%M z8hfTVxYdy?prfWyy@4MNC1n2nG0M3b7V3%Lhj6gc&YU&2%0GWeM`c>(@B0De>`hvbBJVn8o4efl;FivXK{@2 zEO#=^-bkIa2a&Pd3$3M3g>Spq<2$jC!P$aV$4AoYD*RKWY?I}+XCV^pAExQP1`Y5} z(7p*TCAt8Bt(z<8=N3$x7I0U%j~ntAh8*7;+957E*&=>n&-X)nR|FajcW^>F`~rFO zH%N+W#6>rER|L{?i{(qb4Aq78h)`-}iE;`3h6Q{IDsFobjz&kuK=m5b*fGPCX3i^C z{UVw%LmVKc^P<5fZ0eeh#cAcPB_YS{jpJO8trZ6c4}CU#m&4^dk=_Q!Tp`}^ws{gT zq)K(}U6&RW3)6GKC|A1|B`+SoILP%dD{dvpeVn=C!-&qxvzzgIbRU6XW|edL^cCPy zCUuDL2*Hn^09y(v9C^Yn5&E(m)`7<+&&ndYYCIejX5AYT zlWS1iMa5Csj4KH9T=vSUI>+=S;zAkw^jh#gv{APvMhLN2S%oK?a}ZyJp$3bUdpWE!sIGL6@~%CZO&#HoMfjeX=O) z{c$##rDKYiL&h`8IT5YkoE5el%nH@6+OJjgCD^QPEx2V$psQ&BfD^wDwuT!L?FmPE zq7R>T^FFp&p;31Oyy5O6 z%$AieWi7F`^O(L#)ZO%fY7d8pa;Px_(y5>$VRJlk%+{D#(acn(Bt}}?zDs*z?-f>= z)8cqomx&J_DEU+k%CKCWeK~%1u#`8-$*ba(DH#I~*nJwwow-NVp~`Hk#P8fUyH%SsZLU6?|e0V!rP3wXj$)R|M_e=c4-`_ zw3uGQTc@v6XF=TeEu?e3gG*ZSf65+t`dIc6iNEEY0N!KIhS%9)tD*~qFeC?ruR0NA?WaKYF3YknjO{T z)PS{#rBss_H2@&~e^k>}5uNGZC?W{892GSb!jx`ZsvL4&EJnU8fAqEGt2mFtO=S}; zusBmYE}ZBzrHV8%I0jZT^Yw4&2EW!FY?wV+u-tpap?3Yx%3Vd#W?o|Nii4n>mR=?= zK2zj41z(Mzl3s0p}t54@L!a}~V+0mc%0V|!TQq8}5P5??Hw z89fC%loxgucM`ftJ`R7?;$^67)rgWgE1Wurm<*Tr$QKJ7JX&nq1`i;Ih7&^~E$Kkl zU5C+qlJ1(0?`Yu=KF#zLzDhdbOY}GAj8d?346oXdr*jpS)gIW;99Q@Kh;=V~Dr5Yv z8utY2^pJt62IW^Rz32y-SeSnncc;3!OxEC5DX#D|cPJl<20O#Qh74S_z9%%pe=pY` z(z`l5(IahYxz1j+Xa=E=T-9ao9zCFke42ZBKcBlJ@yvT~+i7?Zk-4iVR1sUpiO9D} zsE}-Oc}7D29ay2z#MW1DVy+@k{7P%F2RiaxXkx#Jbb|zMFS6AT<&Fl zlR24b3RY5ksjQ?@2^`}*7^)yNu@J^~v2G&1<+gsr9x0WfS|PHy37%m}_q-$zbh%9C z9B=U8+KX;j_;rlP$iyeJZc|efzB8WixGa10c9WoN5>NW5noBl=vnbYuAF+1AXkCzZ zy|c>zoM1x?6KK!g%^+|R<2Rnnpvx&;JGYw;Ba$u#Kcm}m9dxSMDUMLAvM`N`cUVo; zx%a56ao~JpgZ(4Fc!r*bJ*4!DJw$yTJfXC73Wyb%o9%gB<+Ms?#!K0h`xv zu5rBQ655q)Hgd(hoHxviL~=jWgwc4z^nDTrg=iIJDG6YsbePQVJ8UxvMhIJk((y&TNgTimp@a?2SnOC52q?0`Y?fG@jvv%v0;37o#yUw)AX~Atta%;+7 z2RNvrHFOM}&9k!#F682kT?r?jZk8nFc1o%WiRy0} zn`2#ypxAbh%p0dzG|FmJZOn@vUElYyo|93vDkwtLFukx?u?2Nw<%(R!$>sW@{!7$6 zV-|dO;KJMx#OpD?99G@d#+Z2Rg9Ogj)%g1!hWI;nm)8^30CIpg~%B(@R3n}{u0@pIsYu-FC`d=K1` z89&Ew2#sxg{}1us%8s8iZ3vHTCT>cDZhZ9SCv3jSHq*DwxgE+M3z4t9SwWV+Vcn7? zJ1KXL+uM}QlmYx#FG`{O+3>y7$<8Tk+sU3?|J})#{b?un&LLo%d!FvM+?%J-zea+c zPIex0+fFVs{8uO6A9*`H?A*V%J*-pu_*Z%OwqX9Xn;YoSQX&ihuuy)Cl @@ -15,4 +15,4 @@ moduleid: nl.vanvianen.android.gcm guid: A2371685-B58E-42E4-8403-DF23A877FF0C platform: android minsdk: 5.1.2.GA -architectures: armeabi;armeabi-v7a;x86;x86_64 +architectures: armeabi armeabi-v7a x86 \ No newline at end of file diff --git a/android/src/nl/vanvianen/android/gcm/ADMMessageHandler.java b/android/src/nl/vanvianen/android/gcm/ADMMessageHandler.java new file mode 100644 index 0000000..2e74b7c --- /dev/null +++ b/android/src/nl/vanvianen/android/gcm/ADMMessageHandler.java @@ -0,0 +1,65 @@ +package nl.vanvianen.android.gcm; + +import java.util.HashMap; + +import com.amazon.device.messaging.ADMMessageHandlerBase; + +import android.content.Intent; +import android.util.Log; + +public class ADMMessageHandler extends ADMMessageHandlerBase { + + // Standard Debugging variables + private static final String LCAT = "ADMMessageHandler"; + + public ADMMessageHandler() { + super(ADMMessageHandler.class.getName()); + } + + public static class ADMMessageReceiver extends + com.amazon.device.messaging.ADMMessageReceiver { + public ADMMessageReceiver() { + super(ADMMessageHandler.class); + } + } + + public void onCreate() { + super.onCreate(); + } + + @Override + protected void onRegistered(final String registrationId) { + Log.d(LCAT, "Registered: " + registrationId); + + if (GCMModule.getInstance() != null) { + GCMModule.getInstance().sendSuccess(registrationId); + } + } + + @Override + protected void onUnregistered(final String registrationId) { + Log.i(LCAT, "Unregistered"); + + if (GCMModule.getInstance() != null) { + GCMModule.getInstance().fireEvent(GCMModule.UNREGISTER_EVENT, + new HashMap()); + } + } + + @Override + protected void onRegistrationError(final String errorId) { + Log.d(LCAT, "RegistrationError: " + errorId); + + if (GCMModule.getInstance() != null) { + GCMModule.getInstance().sendError( + "ADM registration failed with code " + errorId); + } + } + + @Override + protected void onMessage(final Intent intent) { + Log.d(LCAT, "Push notification received"); + + NotificationBuilder.build(this, intent); + } +} diff --git a/src/nl/vanvianen/android/gcm/AppStateListener.java b/android/src/nl/vanvianen/android/gcm/AppStateListener.java similarity index 100% rename from src/nl/vanvianen/android/gcm/AppStateListener.java rename to android/src/nl/vanvianen/android/gcm/AppStateListener.java diff --git a/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java b/android/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java similarity index 100% rename from src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java rename to android/src/nl/vanvianen/android/gcm/GCMBroadcastReceiver.java diff --git a/android/src/nl/vanvianen/android/gcm/GCMIntentService.java b/android/src/nl/vanvianen/android/gcm/GCMIntentService.java new file mode 100755 index 0000000..057503b --- /dev/null +++ b/android/src/nl/vanvianen/android/gcm/GCMIntentService.java @@ -0,0 +1,76 @@ +/** + * 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; +import android.content.Intent; +import com.google.android.gcm.GCMBaseIntentService; +import org.appcelerator.kroll.common.Log; + +import java.util.HashMap; + +public class GCMIntentService extends GCMBaseIntentService { + + private static final String LCAT = "GCMIntentService"; + + 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(GCMModule.UNREGISTER_EVENT, + new HashMap()); + } + + @Override + @SuppressWarnings("unchecked") + protected void onMessage(Context context, Intent intent) { + Log.d(LCAT, "Push notification received"); + + NotificationBuilder.build(this, intent); + } + + @Override + public void onError(Context context, String errorId) { + Log.e(LCAT, "Error: " + errorId); + + if (GCMModule.getInstance() != null) { + GCMModule.getInstance().sendError(errorId); + } + } + + @Override + public boolean onRecoverableError(Context context, String errorId) { + Log.e(LCAT, "RecoverableError: " + errorId); + + if (GCMModule.getInstance() != null) { + GCMModule.getInstance().sendError(errorId); + } + + return true; + } +} diff --git a/android/src/nl/vanvianen/android/gcm/GCMModule.java b/android/src/nl/vanvianen/android/gcm/GCMModule.java new file mode 100644 index 0000000..bf4ff2b --- /dev/null +++ b/android/src/nl/vanvianen/android/gcm/GCMModule.java @@ -0,0 +1,428 @@ +/** + * 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.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 com.google.gson.Gson; +import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollFunction; +import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.annotations.Kroll; +import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiApplication; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +@Kroll.module(name = "Gcm", id = "nl.vanvianen.android.gcm") +public class GCMModule extends KrollModule { + // Standard Debugging variables + private static final String LCAT = "GCMModule"; + + private static GCMModule instance = null; + private static AppStateListener appStateListener = null; + private static Object adm = null; + private static boolean IsADMSupported = false; + + /* Callbacks for push notifications */ + private KrollFunction successCallback = null; + private KrollFunction errorCallback = null; + private KrollFunction messageCallback = null; + + /* Callbacks for topics */ + private KrollFunction successTopicCallback = null; + private KrollFunction errorTopicCallback = null; + private KrollFunction topicCallback = null; + + public static final String LAST_DATA = "nl.vanvianen.android.gcm.last_data"; + public static final String NOTIFICATION_SETTINGS = "nl.vanvianen.android.gcm.notification_settings"; + public static final String UNREGISTER_EVENT = "unregister"; + + public GCMModule() { + super(); + instance = this; + if (appStateListener == null) { + appStateListener = new AppStateListener(); + TiApplication.addActivityTransitionListener(appStateListener); + } + + try { + Class.forName("com.amazon.device.messaging.ADM"); + IsADMSupported = true; + adm = (Object) new com.amazon.device.messaging.ADM( + TiApplication.getInstance()); + Log.i(LCAT, "adm supported"); + } catch (Exception e) { + Log.e(LCAT, e.getMessage()); + e.printStackTrace(); + } + } + + public boolean isInForeground() { + return AppStateListener.oneActivityIsResumed; + } + + @Kroll.method + public boolean isADMSupported() { + return IsADMSupported; + } + + @Kroll.method + @SuppressWarnings("unchecked") + public void registerPush(HashMap options) { + + Log.d(LCAT, "registerPush called"); + + Map notificationSettings = (Map) options + .get("notificationSettings"); + successCallback = (KrollFunction) options.get("success"); + errorCallback = (KrollFunction) options.get("error"); + messageCallback = (KrollFunction) options.get("callback"); + + /* Store notification settings in global Ti.App properties */ + JSONObject json = new JSONObject(notificationSettings); + TiApplication.getInstance().getAppProperties() + .setString(GCMModule.NOTIFICATION_SETTINGS, json.toString()); + + String registrationId; + + if (isADMSupported()) { + + registrationId = getRegistrationId(); + if (registrationId != null && registrationId.length() > 0) { + sendSuccess(registrationId); + } else { + ((com.amazon.device.messaging.ADM) adm).startRegister(); + } + + } else { + String senderId = (String) options.get("senderId"); + if (senderId != null) { + GCMRegistrar.register(TiApplication.getInstance(), senderId); + + 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"); + } + } + } + + @Kroll.method + public void unregister() { + Log.d(LCAT, "unregister called (" + (instance != null) + ")"); + + try { + if (isADMSupported()) { + ((com.amazon.device.messaging.ADM) adm).startUnregister(); + } else { + GCMRegistrar.unregister(TiApplication.getInstance()); + } + } catch (Exception ex) { + Log.e(LCAT, "Cannot unregister from push: " + ex.getMessage()); + } + } + + @Kroll.method + @Kroll.getProperty + public String getRegistrationId() { + Log.d(LCAT, "get registrationId property"); + if (isADMSupported()) { + return ((com.amazon.device.messaging.ADM) adm).getRegistrationId(); + } else { + return GCMRegistrar.getRegistrationId(TiApplication.getInstance()); + } + } + + @Kroll.method + 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"); + + if (options.get("success") != null) { + successTopicCallback = (KrollFunction) options.get("success"); + } + if (options.get("error") != null) { + errorTopicCallback = (KrollFunction) options.get("error"); + } + if (options.get("callback") != null) { + topicCallback = (KrollFunction) options.get("callback"); + } + + if (topic == null || !topic.startsWith("/topics/")) { + sendError(errorTopicCallback, + "No or invalid topic specified, should start with /topics/"); + } + + 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); + } + } + return null; + } + }.execute(); + } else { + sendError(errorTopicCallback, + "No GCM senderId specified; get it from the Google Play Developer Console"); + } + } + + @Kroll.method + public void unsubscribe(final HashMap options) { + // unsubscripe from a topic + final String senderId = (String) options.get("senderId"); + final 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 (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()); + } + return null; + } + }.execute(); + } + } + + 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; + } + } + + @Kroll.method + @Kroll.getProperty + @SuppressWarnings("unchecked") + public KrollDict getLastData() { + Map map = new Gson().fromJson(TiApplication.getInstance() + .getAppProperties().getString(LAST_DATA, null), Map.class); + return map != null ? new KrollDict(map) : null; + } + + @Kroll.method + public void clearLastData() { + TiApplication.getInstance().getAppProperties() + .removeProperty(LAST_DATA); + } + + /** + * Cancel a notification by the id given in the payload. + * + * @param notificationId + */ + @Kroll.method + public void cancelNotificationById(int notificationId) { + try { + NotificationManager notificationManager = (NotificationManager) TiApplication + .getInstance().getApplicationContext() + .getSystemService(TiApplication.NOTIFICATION_SERVICE); + notificationManager.cancel(notificationId); + Log.i(LCAT, "Notification " + notificationId + + " cleared successfully"); + } catch (Exception ex) { + Log.e(LCAT, "Cannot cancel notification:" + notificationId + + " Error: " + ex.getMessage()); + } + } + + @Kroll.method + @Kroll.getProperty + @SuppressWarnings("unchecked") + public KrollDict getNotificationSettings() { + Log.d(LCAT, "Getting notification settings"); + Map map = new Gson().fromJson( + TiApplication.getInstance().getAppProperties() + .getString(GCMModule.NOTIFICATION_SETTINGS, null), + Map.class); + return map != null ? new KrollDict(map) : null; + } + + @Kroll.method + @Kroll.setProperty + @SuppressWarnings("unchecked") + public void setNotificationSettings(Map notificationSettings) { + Log.d(LCAT, "Setting notification settings"); + JSONObject json = new JSONObject(notificationSettings); + TiApplication.getInstance().getAppProperties() + .setString(GCMModule.NOTIFICATION_SETTINGS, json.toString()); + } + + public void sendSuccess(String registrationId) { + if (successCallback != null) { + HashMap data = new HashMap(); + data.put("success", true); + data.put("registrationId", registrationId); + successCallback.callAsync(getKrollObject(), data); + } + } + + public void sendError(String error) { + sendError(errorCallback, error); + } + + public void sendError(KrollFunction callback, String error) { + Log.e(LCAT, error); + if (callback != null) { + HashMap data = new HashMap(); + data.put("success", false); + data.put("error", error); + + callback.callAsync(getKrollObject(), data); + } + } + + public void sendMessage(HashMap messageData) { + if (messageCallback != null) { + HashMap data = new HashMap(); + data.put("data", messageData); + data.put("inBackground", !isInForeground()); + + messageCallback.call(getKrollObject(), data); + } else { + Log.e(LCAT, "No callback specified for push notification"); + } + } + + public void sendTopicMessage(HashMap messageData) { + if (topicCallback != null) { + HashMap data = new HashMap(); + data.put("data", messageData); + data.put("inBackground", !isInForeground()); + + topicCallback.call(getKrollObject(), data); + } else { + Log.e(LCAT, "No callback specified for topic subscribe"); + } + } + + @Kroll.onAppCreate + public static void onAppCreate(TiApplication app) { + Log.d(LCAT, "onAppCreate " + app + " (" + (instance != null) + ")"); + } + + @Override + protected void initActivity(Activity activity) { + Log.d(LCAT, "initActivity " + activity + " (" + (instance != null) + + ")"); + super.initActivity(activity); + } + + @Override + public void onResume(Activity activity) { + Log.d(LCAT, "onResume " + activity + " (" + (instance != null) + ")"); + super.onResume(activity); + } + + @Override + public void onPause(Activity activity) { + Log.d(LCAT, "onPause " + activity + " (" + (instance != null) + ")"); + super.onPause(activity); + } + + @Override + public void onDestroy(Activity activity) { + Log.d(LCAT, "onDestroy " + activity + " (" + (instance != null) + ")"); + super.onDestroy(activity); + } + + @Override + public void onStart(Activity activity) { + Log.d(LCAT, "onStart " + activity + " (" + (instance != null) + ")"); + super.onStart(activity); + } + + @Override + public void onStop(Activity activity) { + Log.d(LCAT, "onStop " + activity + " (" + (instance != null) + ")"); + super.onStop(activity); + } + + public static GCMModule getInstance() { + return instance; + } +} \ No newline at end of file diff --git a/android/src/nl/vanvianen/android/gcm/NotificationBuilder.java b/android/src/nl/vanvianen/android/gcm/NotificationBuilder.java new file mode 100644 index 0000000..40adbd0 --- /dev/null +++ b/android/src/nl/vanvianen/android/gcm/NotificationBuilder.java @@ -0,0 +1,511 @@ +package nl.vanvianen.android.gcm; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiApplication; +import org.appcelerator.titanium.util.TiRHelper; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; + +import com.google.gson.Gson; + +public class NotificationBuilder { + + private static final String LCAT = "NotificationHelper"; + + private static final String DEFAULT_TITLE_KEY = "title"; + private static final String DEFAULT_MESSAGE_KEY = "message"; + private static final String DEFAULT_TICKER_KEY = "ticker"; + + public static void build(Context context, Intent intent) { + + boolean isTopic = false; + + HashMap data = new HashMap(); + for (String key : intent.getExtras().keySet()) { + Object value = intent.getExtras().get(key); + Log.d(LCAT, "Message key: \"" + key + "\" value: \"" + value + "\""); + + if (key.equals("from") && value instanceof String + && ((String) value).startsWith("/topics/")) { + isTopic = true; + } + + String eventKey = key.startsWith("data.") ? key.substring(5) : key; + data.put(eventKey, intent.getExtras().get(key)); + + if (value instanceof String && ((String) value).startsWith("{")) { + Log.d(LCAT, "Parsing JSON string..."); + try { + JSONObject json = new JSONObject((String) value); + + Iterator keys = json.keys(); + while (keys.hasNext()) { + String jKey = keys.next(); + String jValue = json.getString(jKey); + Log.d(LCAT, "JSON key: \"" + jKey + "\" value: \"" + + jValue + "\""); + + data.put(jKey, jValue); + } + } catch (JSONException ex) { + Log.d(LCAT, "JSON error: " + ex.getMessage()); + } + } + } + + /* + * 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(data); + TiApplication.getInstance().getAppProperties() + .setString(GCMModule.LAST_DATA, json.toString()); + + /* Get settings from notification object */ + int smallIcon = 0; + int largeIcon = 0; + String sound = null; + boolean vibrate = false; + boolean insistent = false; + String group = null; + boolean localOnly = true; + int priority = 0; + boolean bigText = false; + int notificationId = 1; + + Integer ledOn = null; + Integer ledOff = null; + + String titleKey = DEFAULT_TITLE_KEY; + String messageKey = DEFAULT_MESSAGE_KEY; + String tickerKey = DEFAULT_TICKER_KEY; + String title = null; + String message = null; + String ticker = null; + + boolean backgroundOnly = false; + + Map notificationSettings = new Gson().fromJson( + TiApplication.getInstance().getAppProperties() + .getString(GCMModule.NOTIFICATION_SETTINGS, null), + Map.class); + if (notificationSettings != null) { + if (notificationSettings.get("smallIcon") instanceof String) { + smallIcon = getResource("drawable", + (String) notificationSettings.get("smallIcon")); + } else { + Log.e(LCAT, "Invalid setting smallIcon, should be String"); + } + + if (notificationSettings.get("largeIcon") instanceof String) { + largeIcon = getResource("drawable", + (String) notificationSettings.get("largeIcon")); + } else { + Log.e(LCAT, "Invalid setting largeIcon, should be String"); + } + + if (notificationSettings.get("sound") != null) { + if (notificationSettings.get("sound") instanceof String) { + sound = (String) notificationSettings.get("sound"); + } else { + Log.e(LCAT, "Invalid setting sound, should be String"); + } + } + + if (notificationSettings.get("vibrate") != null) { + if (notificationSettings.get("vibrate") instanceof Boolean) { + vibrate = (Boolean) notificationSettings.get("vibrate"); + } else { + Log.e(LCAT, "Invalid setting vibrate, should be Boolean"); + } + } + + if (notificationSettings.get("insistent") != null) { + if (notificationSettings.get("insistent") instanceof Boolean) { + insistent = (Boolean) notificationSettings.get("insistent"); + } else { + Log.e(LCAT, "Invalid setting insistent, should be Boolean"); + } + } + + if (notificationSettings.get("group") != null) { + if (notificationSettings.get("group") instanceof String) { + group = (String) notificationSettings.get("group"); + } else { + Log.e(LCAT, "Invalid setting group, should be String"); + } + } + + if (notificationSettings.get("localOnly") != null) { + if (notificationSettings.get("localOnly") instanceof Boolean) { + localOnly = (Boolean) notificationSettings.get("localOnly"); + } else { + Log.e(LCAT, "Invalid setting localOnly, should be Boolean"); + } + } + + if (notificationSettings.get("priority") != null) { + if (notificationSettings.get("priority") instanceof Integer) { + priority = (Integer) notificationSettings.get("priority"); + } else if (notificationSettings.get("priority") instanceof Double) { + priority = ((Double) notificationSettings.get("priority")) + .intValue(); + } else { + Log.e(LCAT, + "Invalid setting priority, should be an integer, between PRIORITY_MIN (" + + NotificationCompat.PRIORITY_MIN + + ") and PRIORITY_MAX (" + + NotificationCompat.PRIORITY_MAX + ")"); + } + } + + if (notificationSettings.get("bigText") != null) { + if (notificationSettings.get("bigText") instanceof Boolean) { + bigText = (Boolean) notificationSettings.get("bigText"); + } else { + Log.e(LCAT, "Invalid setting bigText, should be Boolean"); + } + } + + if (notificationSettings.get("titleKey") != null) { + if (notificationSettings.get("titleKey") instanceof String) { + titleKey = (String) notificationSettings.get("titleKey"); + } else { + Log.e(LCAT, "Invalid setting titleKey, should be String"); + } + } + + if (notificationSettings.get("messageKey") != null) { + if (notificationSettings.get("messageKey") instanceof String) { + messageKey = (String) notificationSettings + .get("messageKey"); + } else { + Log.e(LCAT, "Invalid setting messageKey, should be String"); + } + } + + if (notificationSettings.get("tickerKey") != null) { + if (notificationSettings.get("tickerKey") instanceof String) { + tickerKey = (String) notificationSettings.get("tickerKey"); + } else { + Log.e(LCAT, "Invalid setting tickerKey, should be String"); + } + } + + if (notificationSettings.get("title") != null) { + if (notificationSettings.get("title") instanceof String) { + title = (String) notificationSettings.get("title"); + } else { + Log.e(LCAT, "Invalid setting title, should be String"); + } + } + + if (notificationSettings.get("message") != null) { + if (notificationSettings.get("message") instanceof String) { + message = (String) notificationSettings.get("message"); + } else { + Log.e(LCAT, "Invalid setting message, should be String"); + } + } + + if (notificationSettings.get("ticker") != null) { + if (notificationSettings.get("ticker") instanceof String) { + ticker = (String) notificationSettings.get("ticker"); + } else { + Log.e(LCAT, "Invalid setting ticker, should be String"); + } + } + + if (notificationSettings.get("ledOn") != null) { + if (notificationSettings.get("ledOn") instanceof Integer) { + ledOn = (Integer) notificationSettings.get("ledOn"); + if (ledOn < 0) { + Log.e(LCAT, "Invalid setting ledOn, should be positive"); + ledOn = null; + } + } else { + Log.e(LCAT, "Invalid setting ledOn, should be Integer"); + } + } + + if (notificationSettings.get("ledOff") != null) { + if (notificationSettings.get("ledOff") instanceof Integer) { + ledOff = (Integer) notificationSettings.get("ledOff"); + if (ledOff < 0) { + Log.e(LCAT, + "Invalid setting ledOff, should be positive"); + ledOff = null; + } + } else { + Log.e(LCAT, "Invalid setting ledOff, should be Integer"); + } + } + + if (notificationSettings.get("backgroundOnly") != null) { + if (notificationSettings.get("backgroundOnly") instanceof Boolean) { + backgroundOnly = (Boolean) notificationSettings + .get("backgroundOnly"); + } else { + Log.e(LCAT, + "Invalid setting backgroundOnly, should be Boolean"); + } + } + + if (notificationSettings.get("notificationId") != null) { + if (notificationSettings.get("notificationId") instanceof Integer) { + notificationId = (Integer) notificationSettings + .get("notificationId"); + } else { + Log.e(LCAT, + "Invalid setting notificationId, should be Integer"); + } + } + + } else { + Log.d(LCAT, "No notification settings found"); + } + + /* If icon not found, default to appicon */ + if (smallIcon == 0) { + smallIcon = getResource("drawable", "appicon"); + } + + /* If large icon not found, default to icon */ + if (largeIcon == 0) { + largeIcon = smallIcon; + } + + /* Create intent to (re)start the app's root activity */ + String pkg = TiApplication.getInstance().getApplicationContext() + .getPackageName(); + Intent launcherIntent = TiApplication.getInstance() + .getApplicationContext().getPackageManager() + .getLaunchIntentForPackage(pkg); + launcherIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + /* + * Grab notification content from data according to provided keys if not + * already set + */ + if (title == null && titleKey != null) { + title = (String) data.get(titleKey); + } + if (message == null && messageKey != null) { + message = (String) data.get(messageKey); + } + if (ticker == null && tickerKey != null) { + ticker = (String) data.get(tickerKey); + } + + Log.i(LCAT, "Title: " + title); + Log.i(LCAT, "Message: " + message); + Log.i(LCAT, "Ticker: " + ticker); + + /* Check for app state */ + if (GCMModule.getInstance() != null) { + /* Send data to app */ + if (isTopic) { + GCMModule.getInstance().sendTopicMessage(data); + } else { + GCMModule.getInstance().sendMessage(data); + } + /* + * Do not create notification if backgroundOnly and app is in + * foreground + */ + if (backgroundOnly && GCMModule.getInstance().isInForeground()) { + Log.d(LCAT, + "Notification received in foreground, no need for notification."); + return; + } + } + + if (message == null) { + Log.d(LCAT, + "Message received but no 'message' specified in push notification payload, so will make this silent"); + } else { + Log.d(LCAT, "Creating notification..."); + + Bitmap bitmap = BitmapFactory.decodeResource( + context.getResources(), largeIcon); + if (bitmap == null) { + Log.d(LCAT, "No large icon found"); + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder( + context) + .setContentTitle(title) + .setContentText(message) + .setTicker(ticker) + .setContentIntent( + PendingIntent + .getActivity(context, 0, launcherIntent, + PendingIntent.FLAG_ONE_SHOT)) + .setSmallIcon(smallIcon).setLargeIcon(bitmap); + + /* + * Name of group to group similar notifications together, can also + * be set in the push notification payload + */ + if (data.get("group") != null) { + group = (String) data.get("group"); + } + if (group != null) { + builder.setGroup(group); + } + Log.i(LCAT, "Group: " + group); + + /* + * Whether notification should be for this device only or bridged to + * other devices, can also be set in the push notification payload + */ + if (data.get("localOnly") != null) { + localOnly = Boolean.valueOf((String) data.get("localOnly")); + } + builder.setLocalOnly(localOnly); + Log.i(LCAT, "LocalOnly: " + localOnly); + + /* + * Specify notification priority, can also be set in the push + * notification payload + */ + if (data.get("priority") != null) { + priority = Integer.parseInt((String) data.get("priority")); + } + if (priority >= NotificationCompat.PRIORITY_MIN + && priority <= NotificationCompat.PRIORITY_MAX) { + builder.setPriority(priority); + Log.i(LCAT, "Priority: " + priority); + } else { + Log.e(LCAT, "Ignored invalid priority " + priority); + } + + /* + * Specify whether bigtext should be used, can also be set in the + * push notification payload + */ + if (data.get("bigText") != null) { + bigText = Boolean.valueOf((String) data.get("bigText")); + } + if (bigText) { + builder.setStyle(new NotificationCompat.BigTextStyle() + .bigText(message)); + } + 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"); + } + + if ("default".equals(sound)) { + Log.i(LCAT, "Sound: default sound"); + notification.defaults |= Notification.DEFAULT_SOUND; + } else if (sound != null) { + Log.i(LCAT, "Sound " + sound); + notification.sound = Uri.parse("android.resource://" + pkg + + "/" + getResource("raw", sound)); + } + + /* Vibrate, can also be set in the push notification payload */ + if (data.get("vibrate") != null) { + vibrate = Boolean.valueOf((String) data.get("vibrate")); + } + if (vibrate) { + notification.defaults |= Notification.DEFAULT_VIBRATE; + } + Log.i(LCAT, "Vibrate: " + vibrate); + + /* Insistent, can also be set in the push notification payload */ + if ("true".equals(data.get("insistent"))) { + insistent = true; + } + if (insistent) { + notification.flags |= Notification.FLAG_INSISTENT; + } + Log.i(LCAT, "Insistent: " + insistent); + + /* + * notificationId, set in push payload to specify multiple + * notifications should be shown. If not specified, subsequent + * notifications "override / overwrite" the older ones + */ + if (data.get("notificationId") != null) { + if (data.get("notificationId") instanceof Integer) { + notificationId = (Integer) data.get("notificationId"); + } else if (data.get("notificationId") instanceof String) { + try { + notificationId = Integer.parseInt((String) data + .get("notificationId")); + } catch (NumberFormatException ex) { + Log.e(LCAT, + "Invalid setting notificationId, should be Integer"); + } + } else { + Log.e(LCAT, + "Invalid setting notificationId, should be Integer"); + } + } + Log.i(LCAT, "Notification ID: " + notificationId); + + /* Specify LED flashing */ + if (ledOn != null || ledOff != null) { + notification.flags |= Notification.FLAG_SHOW_LIGHTS; + if (ledOn != null) { + notification.ledOnMS = ledOn; + } + if (ledOff != null) { + notification.ledOffMS = ledOff; + } + } else { + notification.defaults |= Notification.DEFAULT_LIGHTS; + } + + notification.flags |= Notification.FLAG_AUTO_CANCEL; + + ((NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE)).notify( + notificationId, notification); + } + } + + private static int getResource(String type, String name) { + int icon = 0; + if (name != null) { + /* Remove extension from icon */ + int index = name.lastIndexOf("."); + if (index > 0) { + name = name.substring(0, index); + } + try { + icon = TiRHelper.getApplicationResource(type + "." + name); + } catch (TiRHelper.ResourceNotFoundException ex) { + Log.e(LCAT, type + "." + name + + " not found; make sure it's in platform/android/res/" + + type); + } + } + + return icon; + } + +} diff --git a/android/timodule.xml b/android/timodule.xml new file mode 100644 index 0000000..f828076 --- /dev/null +++ b/android/timodule.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.properties b/build.properties deleted file mode 100644 index c196372..0000000 --- a/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -titanium.platform=/Users/jeroen/Library/Application Support/Titanium/mobilesdk/osx/5.3.0.GA/android -android.platform=/Users/jeroen/Library/android-sdk-macosx/platforms/android-14 -google.apis=/Users/jeroen/Library/android-sdk-macosx/add-ons/addon-google_apis-google-15 -android.ndk=/Users/jeroen/Library/android-ndk-r12b 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..865d662 100644 --- a/documentation/index.md +++ b/documentation/index.md @@ -9,6 +9,15 @@ A Titanium module for registering a device with Google Cloud Messaging and handl 1. Send a server push notification with your preferred server-side technology to the registrationId returned while registering your device. 1. The callback you specified will then be called. +In order to configure ADM properly, you must include the plugin as shown below and replace the conent of `api_key.txt` with your api key. + +```xml + + ti.alloy + gcmpush + +``` + This module does not require any tiapp.xml properties, all configuration is done in Javascript. ## Example server-side code to send a push notification ## diff --git a/hooks/README b/hooks/README deleted file mode 100644 index 66b10a8..0000000 --- a/hooks/README +++ /dev/null @@ -1 +0,0 @@ -These files are not yet supported as of 1.4.0 but will be in a near future release. diff --git a/hooks/add.py b/hooks/add.py deleted file mode 100644 index 04e1c1d..0000000 --- a/hooks/add.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -# -# This is the module project add hook that will be -# called when your module is added to a project -# -import os, sys - -def dequote(s): - if s[0:1] == '"': - return s[1:-1] - return s - -def main(args,argc): - # You will get the following command line arguments - # in the following order: - # - # project_dir = the full path to the project root directory - # project_type = the type of project (desktop, mobile, ipad) - # project_name = the name of the project - # - project_dir = dequote(os.path.expanduser(args[1])) - project_type = dequote(args[2]) - project_name = dequote(args[3]) - - # TODO: write your add hook here (optional) - - - # exit - sys.exit(0) - - - -if __name__ == '__main__': - main(sys.argv,len(sys.argv)) - diff --git a/hooks/install.py b/hooks/install.py deleted file mode 100644 index b423fe9..0000000 --- a/hooks/install.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -# This is the module install hook that will be -# called when your module is first installed -# -import os, sys - -def main(args,argc): - - # TODO: write your install hook here (optional) - - # exit - sys.exit(0) - - - -if __name__ == '__main__': - main(sys.argv,len(sys.argv)) - diff --git a/hooks/remove.py b/hooks/remove.py deleted file mode 100644 index f92a234..0000000 --- a/hooks/remove.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -# This is the module project remove hook that will be -# called when your module is remove from a project -# -import os, sys - -def dequote(s): - if s[0:1] == '"': - return s[1:-1] - return s - -def main(args,argc): - # You will get the following command line arguments - # in the following order: - # - # project_dir = the full path to the project root directory - # project_type = the type of project (desktop, mobile, ipad) - # project_name = the name of the project - # - project_dir = dequote(os.path.expanduser(args[1])) - project_type = dequote(args[2]) - project_name = dequote(args[3]) - - # TODO: write your remove hook here (optional) - - # exit - sys.exit(0) - - - -if __name__ == '__main__': - main(sys.argv,len(sys.argv)) - diff --git a/hooks/uninstall.py b/hooks/uninstall.py deleted file mode 100644 index a7ffd91..0000000 --- a/hooks/uninstall.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# This is the module uninstall hook that will be -# called when your module is uninstalled -# -import os, sys - -def main(args,argc): - - # TODO: write your uninstall hook here (optional) - - # exit - sys.exit(0) - - -if __name__ == '__main__': - main(sys.argv,len(sys.argv)) - diff --git a/platform/README b/platform/README deleted file mode 100644 index 7ac991c..0000000 --- a/platform/README +++ /dev/null @@ -1,3 +0,0 @@ -You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. - -When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. diff --git a/platform/android/assets/api_key.txt b/platform/android/assets/api_key.txt new file mode 100644 index 0000000..ecc9d7a --- /dev/null +++ b/platform/android/assets/api_key.txt @@ -0,0 +1 @@ +Replace this line with your API Key. \ No newline at end of file diff --git a/plugins/gcmpush/hooks/gcmpush.js b/plugins/gcmpush/hooks/gcmpush.js new file mode 100644 index 0000000..5c14569 --- /dev/null +++ b/plugins/gcmpush/hooks/gcmpush.js @@ -0,0 +1,53 @@ +exports.cliVersion = ">=3.X"; + +var LCAT = "GCMPush", + fs = require("fs"), + path = require("path"), + AndroidManifest = require("androidmanifest"); + +exports.init = function(logger, config, cli, nodeappc) { + + var opts = config.appc.opts; + + /** + * delete amazon-device-messaging stub library from libs + * ToDo: handle with Ant at module build time + */ + cli.on("build.pre.compile", function(data, done) { + if (opts.platform === "android") { + var moduleId = "nl.vanvianen.android.gcm", + modules = cli.tiapp.modules, + modulePath; + for (var i in modules) { + var module = modules[i]; + if (module.id === moduleId) { + modulePath = path.join(opts.projectDir, "modules/android", moduleId, module.version, "lib"); + break; + } + } + var jars = fs.readdirSync(modulePath); + for (var i in jars) { + var jar = jars[i]; + if (jar.indexOf("amazon-device-messaging") >= 0) { + fs.unlinkSync(path.join(modulePath, jar)); + break; + } + } + } + done(); + }); + + /** + * Appc cli eliminates custom tags + */ + cli.on("build.android.writeAndroidManifest", function(data, done) { + var manifestFilePath = this.androidManifestFile, + manifest = new AndroidManifest().readFile(manifestFilePath); + + manifest.$("manifest").attr("xmlns:amazon", "http://schemas.amazon.com/apk/res/android"); + manifest.$("application").append(""); + manifest.writeFile(manifestFilePath); + + done(); + }); +}; diff --git a/plugins/gcmpush/package.json b/plugins/gcmpush/package.json new file mode 100644 index 0000000..801ba3c --- /dev/null +++ b/plugins/gcmpush/package.json @@ -0,0 +1,10 @@ +{ + "id": "gcmpush", + "name": "gcmpush", + "author": "Manoj Kumar", + "version": "1.0.0", + "copyright": "Copyright (c) 2016", + "dependencies": { + "androidmanifest": "^2.0.0" + } +} diff --git a/src/nl/vanvianen/android/gcm/GCMIntentService.java b/src/nl/vanvianen/android/gcm/GCMIntentService.java deleted file mode 100755 index 87082ea..0000000 --- a/src/nl/vanvianen/android/gcm/GCMIntentService.java +++ /dev/null @@ -1,508 +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.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.support.v4.app.NotificationCompat; -import com.google.android.gcm.GCMBaseIntentService; -import com.google.gson.Gson; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiApplication; -import org.appcelerator.titanium.util.TiRHelper; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -public class GCMIntentService extends GCMBaseIntentService { - - private static final String LCAT = "GCMIntentService"; - - private static final String UNREGISTER_EVENT = "unregister"; - - private static final String DEFAULT_TITLE_KEY = "title"; - private static final String DEFAULT_MESSAGE_KEY = "message"; - private static final String DEFAULT_TICKER_KEY = "ticker"; - - 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()); - } - - private int getResource(String type, String name) { - int icon = 0; - if (name != null) { - /* Remove extension from icon */ - int index = name.lastIndexOf("."); - if (index > 0) { - name = name.substring(0, index); - } - try { - icon = TiRHelper.getApplicationResource(type + "." + name); - } catch (TiRHelper.ResourceNotFoundException ex) { - Log.e(LCAT, type + "." + name + " not found; make sure it's in platform/android/res/" + type); - } - } - - return icon; - } - - @Override - @SuppressWarnings("unchecked") - protected void onMessage(Context context, Intent intent) { - Log.d(LCAT, "Push notification received"); - - boolean isTopic = false; - - HashMap data = new HashMap(); - for (String key : intent.getExtras().keySet()) { - Object value = intent.getExtras().get(key); - Log.d(LCAT, "Message key: \"" + key + "\" value: \"" + value + "\""); - - if (key.equals("from") && value instanceof String && ((String) value).startsWith("/topics/")) { - isTopic = true; - } - - String eventKey = key.startsWith("data.") ? key.substring(5) : key; - data.put(eventKey, intent.getExtras().get(key)); - - if (value instanceof String && ((String) value).startsWith("{")) { - Log.d(LCAT, "Parsing JSON string..."); - try { - JSONObject json = new JSONObject((String) value); - - Iterator keys = json.keys(); - while (keys.hasNext()) { - String jKey = keys.next(); - String jValue = json.getString(jKey); - Log.d(LCAT, "JSON key: \"" + jKey + "\" value: \"" + jValue + "\""); - - data.put(jKey, jValue); - } - } catch(JSONException ex) { - Log.d(LCAT, "JSON error: " + ex.getMessage()); - } - } - } - - /* 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(data); - TiApplication.getInstance().getAppProperties().setString(GCMModule.LAST_DATA, json.toString()); - - /* Get settings from notification object */ - int smallIcon = 0; - int largeIcon = 0; - String sound = null; - boolean vibrate = false; - boolean insistent = false; - String group = null; - boolean localOnly = true; - int priority = 0; - boolean bigText = false; - int notificationId = 1; - - Integer ledOn = null; - Integer ledOff = null; - - String titleKey = DEFAULT_TITLE_KEY; - String messageKey = DEFAULT_MESSAGE_KEY; - String tickerKey = DEFAULT_TICKER_KEY; - String title = null; - String message = null; - String ticker = null; - - boolean backgroundOnly = false; - - Map notificationSettings = new Gson().fromJson(TiApplication.getInstance().getAppProperties().getString(GCMModule.NOTIFICATION_SETTINGS, null), Map.class); - if (notificationSettings != null) { - if (notificationSettings.get("smallIcon") instanceof String) { - smallIcon = getResource("drawable", (String) notificationSettings.get("smallIcon")); - } else { - Log.e(LCAT, "Invalid setting smallIcon, should be String"); - } - - if (notificationSettings.get("largeIcon") instanceof String) { - largeIcon = getResource("drawable", (String) notificationSettings.get("largeIcon")); - } else { - Log.e(LCAT, "Invalid setting largeIcon, should be String"); - } - - if (notificationSettings.get("sound") != null) { - if (notificationSettings.get("sound") instanceof String) { - sound = (String) notificationSettings.get("sound"); - } else { - Log.e(LCAT, "Invalid setting sound, should be String"); - } - } - - if (notificationSettings.get("vibrate") != null) { - if (notificationSettings.get("vibrate") instanceof Boolean) { - vibrate = (Boolean) notificationSettings.get("vibrate"); - } else { - Log.e(LCAT, "Invalid setting vibrate, should be Boolean"); - } - } - - if (notificationSettings.get("insistent") != null) { - if (notificationSettings.get("insistent") instanceof Boolean) { - insistent = (Boolean) notificationSettings.get("insistent"); - } else { - Log.e(LCAT, "Invalid setting insistent, should be Boolean"); - } - } - - if (notificationSettings.get("group") != null) { - if (notificationSettings.get("group") instanceof String) { - group = (String) notificationSettings.get("group"); - } else { - Log.e(LCAT, "Invalid setting group, should be String"); - } - } - - if (notificationSettings.get("localOnly") != null) { - if (notificationSettings.get("localOnly") instanceof Boolean) { - localOnly = (Boolean) notificationSettings.get("localOnly"); - } else { - Log.e(LCAT, "Invalid setting localOnly, should be Boolean"); - } - } - - if (notificationSettings.get("priority") != null) { - if (notificationSettings.get("priority") instanceof Integer) { - priority = (Integer) notificationSettings.get("priority"); - } else if (notificationSettings.get("priority") instanceof Double) { - priority = ((Double) notificationSettings.get("priority")).intValue(); - } else { - Log.e(LCAT, "Invalid setting priority, should be an integer, between PRIORITY_MIN (" + NotificationCompat.PRIORITY_MIN + ") and PRIORITY_MAX (" + NotificationCompat.PRIORITY_MAX + ")"); - } - } - - if (notificationSettings.get("bigText") != null) { - if (notificationSettings.get("bigText") instanceof Boolean) { - bigText = (Boolean) notificationSettings.get("bigText"); - } else { - Log.e(LCAT, "Invalid setting bigText, should be Boolean"); - } - } - - if (notificationSettings.get("titleKey") != null) { - if (notificationSettings.get("titleKey") instanceof String) { - titleKey = (String) notificationSettings.get("titleKey"); - } else { - Log.e(LCAT, "Invalid setting titleKey, should be String"); - } - } - - if (notificationSettings.get("messageKey") != null) { - if (notificationSettings.get("messageKey") instanceof String) { - messageKey = (String) notificationSettings.get("messageKey"); - } else { - Log.e(LCAT, "Invalid setting messageKey, should be String"); - } - } - - if (notificationSettings.get("tickerKey") != null) { - if (notificationSettings.get("tickerKey") instanceof String) { - tickerKey = (String) notificationSettings.get("tickerKey"); - } else { - Log.e(LCAT, "Invalid setting tickerKey, should be String"); - } - } - - if (notificationSettings.get("title") != null) { - if (notificationSettings.get("title") instanceof String) { - title = (String) notificationSettings.get("title"); - } else { - Log.e(LCAT, "Invalid setting title, should be String"); - } - } - - if (notificationSettings.get("message") != null) { - if (notificationSettings.get("message") instanceof String) { - message = (String) notificationSettings.get("message"); - } else { - Log.e(LCAT, "Invalid setting message, should be String"); - } - } - - if (notificationSettings.get("ticker") != null) { - if (notificationSettings.get("ticker") instanceof String) { - ticker = (String) notificationSettings.get("ticker"); - } else { - Log.e(LCAT, "Invalid setting ticker, should be String"); - } - } - - if (notificationSettings.get("ledOn") != null) { - if (notificationSettings.get("ledOn") instanceof Integer) { - ledOn = (Integer) notificationSettings.get("ledOn"); - if (ledOn < 0) { - Log.e(LCAT, "Invalid setting ledOn, should be positive"); - ledOn = null; - } - } else { - Log.e(LCAT, "Invalid setting ledOn, should be Integer"); - } - } - - if (notificationSettings.get("ledOff") != null) { - if (notificationSettings.get("ledOff") instanceof Integer) { - ledOff = (Integer) notificationSettings.get("ledOff"); - if (ledOff < 0) { - Log.e(LCAT, "Invalid setting ledOff, should be positive"); - ledOff = null; - } - } else { - Log.e(LCAT, "Invalid setting ledOff, should be Integer"); - } - } - - if (notificationSettings.get("backgroundOnly") != null) { - if (notificationSettings.get("backgroundOnly") instanceof Boolean) { - backgroundOnly = (Boolean) notificationSettings.get("backgroundOnly"); - } else { - Log.e(LCAT, "Invalid setting backgroundOnly, should be Boolean"); - } - } - - if (notificationSettings.get("notificationId") != null) { - if (notificationSettings.get("notificationId") instanceof Integer) { - notificationId = (Integer) notificationSettings.get("notificationId"); - } else { - Log.e(LCAT, "Invalid setting notificationId, should be Integer"); - } - } - - } else { - Log.d(LCAT, "No notification settings found"); - } - - /* If icon not found, default to appicon */ - if (smallIcon == 0) { - smallIcon = getResource("drawable", "appicon"); - } - - /* If large icon not found, default to icon */ - if (largeIcon == 0) { - largeIcon = smallIcon; - } - - /* Create intent to (re)start the app's root activity */ - String pkg = TiApplication.getInstance().getApplicationContext().getPackageName(); - Intent launcherIntent = TiApplication.getInstance().getApplicationContext().getPackageManager().getLaunchIntentForPackage(pkg); - launcherIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - /* Grab notification content from data according to provided keys if not already set */ - if (title == null && titleKey != null) { - title = (String) data.get(titleKey); - } - if (message == null && messageKey != null) { - message = (String) data.get(messageKey); - } - if (ticker == null && tickerKey != null) { - ticker = (String) data.get(tickerKey); - } - - Log.i(LCAT, "Title: " + title); - Log.i(LCAT, "Message: " + message); - Log.i(LCAT, "Ticker: " + ticker); - - /* Check for app state */ - if (GCMModule.getInstance() != null) { - /* Send data to app */ - if (isTopic) { - GCMModule.getInstance().sendTopicMessage(data); - } else { - GCMModule.getInstance().sendMessage(data); - } - /* Do not create notification if backgroundOnly and app is in foreground */ - if (backgroundOnly && GCMModule.getInstance().isInForeground()) { - Log.d(LCAT, "Notification received in foreground, no need for notification."); - return; - } - } - - if (message == null) { - Log.d(LCAT, "Message received but no 'message' specified in push notification payload, so will make this silent"); - } else { - Log.d(LCAT, "Creating notification..."); - - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), largeIcon); - if (bitmap == null) { - Log.d(LCAT, "No large icon found"); - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context) - .setContentTitle(title) - .setContentText(message) - .setTicker(ticker) - .setContentIntent(PendingIntent.getActivity(this, 0, launcherIntent, PendingIntent.FLAG_ONE_SHOT)) - .setSmallIcon(smallIcon) - .setLargeIcon(bitmap); - - /* Name of group to group similar notifications together, can also be set in the push notification payload */ - if (data.get("group") != null) { - group = (String) data.get("group"); - } - if (group != null) { - builder.setGroup(group); - } - Log.i(LCAT, "Group: " + group); - - /* Whether notification should be for this device only or bridged to other devices, can also be set in the push notification payload */ - if (data.get("localOnly") != null) { - localOnly = Boolean.valueOf((String) data.get("localOnly")); - } - builder.setLocalOnly(localOnly); - Log.i(LCAT, "LocalOnly: " + localOnly); - - /* Specify notification priority, can also be set in the push notification payload */ - if (data.get("priority") != null) { - priority = Integer.parseInt((String) data.get("priority")); - } - if (priority >= NotificationCompat.PRIORITY_MIN && priority <= NotificationCompat.PRIORITY_MAX) { - builder.setPriority(priority); - Log.i(LCAT, "Priority: " + priority); - } else { - Log.e(LCAT, "Ignored invalid priority " + priority); - } - - /* Specify whether bigtext should be used, can also be set in the push notification payload */ - if (data.get("bigText") != null) { - bigText = Boolean.valueOf((String) data.get("bigText")); - } - if (bigText) { - builder.setStyle(new NotificationCompat.BigTextStyle().bigText(message)); - } - 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"); - } - - if ("default".equals(sound)) { - Log.i(LCAT, "Sound: default sound"); - notification.defaults |= Notification.DEFAULT_SOUND; - } else if (sound != null) { - Log.i(LCAT, "Sound " + sound); - notification.sound = Uri.parse("android.resource://" + pkg + "/" + getResource("raw", sound)); - } - - /* Vibrate, can also be set in the push notification payload */ - if (data.get("vibrate") != null) { - vibrate = Boolean.valueOf((String) data.get("vibrate")); - } - if (vibrate) { - notification.defaults |= Notification.DEFAULT_VIBRATE; - } - Log.i(LCAT, "Vibrate: " + vibrate); - - /* Insistent, can also be set in the push notification payload */ - if ("true".equals(data.get("insistent"))) { - insistent = true; - } - if (insistent) { - notification.flags |= Notification.FLAG_INSISTENT; - } - Log.i(LCAT, "Insistent: " + insistent); - - /* notificationId, set in push payload to specify multiple notifications should be shown. If not specified, subsequent notifications "override / overwrite" the older ones */ - if (data.get("notificationId") != null) { - if (data.get("notificationId") instanceof Integer) { - notificationId = (Integer) data.get("notificationId"); - } else if (data.get("notificationId") instanceof String) { - try { - notificationId = Integer.parseInt((String) data.get("notificationId")); - } catch (NumberFormatException ex) { - Log.e(LCAT, "Invalid setting notificationId, should be Integer"); - } - } else { - Log.e(LCAT, "Invalid setting notificationId, should be Integer"); - } - } - Log.i(LCAT, "Notification ID: " + notificationId); - - /* Specify LED flashing */ - if (ledOn != null || ledOff != null) { - notification.flags |= Notification.FLAG_SHOW_LIGHTS; - if (ledOn != null) { - notification.ledOnMS = ledOn; - } - if (ledOff != null) { - notification.ledOffMS = ledOff; - } - } else { - notification.defaults |= Notification.DEFAULT_LIGHTS; - } - - notification.flags |= Notification.FLAG_AUTO_CANCEL; - - ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(notificationId, notification); - } - } - - @Override - public void onError(Context context, String errorId) { - Log.e(LCAT, "Error: " + errorId); - - if (GCMModule.getInstance() != null) { - GCMModule.getInstance().sendError(errorId); - } - } - - @Override - public boolean onRecoverableError(Context context, String errorId) { - Log.e(LCAT, "RecoverableError: " + errorId); - - if (GCMModule.getInstance() != null) { - GCMModule.getInstance().sendError(errorId); - } - - return true; - } -} diff --git a/src/nl/vanvianen/android/gcm/GCMModule.java b/src/nl/vanvianen/android/gcm/GCMModule.java deleted file mode 100644 index d5c24b9..0000000 --- a/src/nl/vanvianen/android/gcm/GCMModule.java +++ /dev/null @@ -1,367 +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.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 com.google.gson.Gson; -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollFunction; -import org.appcelerator.kroll.KrollModule; -import org.appcelerator.kroll.annotations.Kroll; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiApplication; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Map; - -@Kroll.module(name = "Gcm", id = "nl.vanvianen.android.gcm") -public class GCMModule extends KrollModule { - // Standard Debugging variables - private static final String LCAT = "GCMModule"; - - private static GCMModule instance = null; - private static AppStateListener appStateListener = null; - - /* Callbacks for push notifications */ - private KrollFunction successCallback = null; - private KrollFunction errorCallback = null; - private KrollFunction messageCallback = null; - - /* Callbacks for topics */ - private KrollFunction successTopicCallback = null; - private KrollFunction errorTopicCallback = null; - private KrollFunction topicCallback = null; - - - public static final String LAST_DATA = "nl.vanvianen.android.gcm.last_data"; - public static final String NOTIFICATION_SETTINGS = "nl.vanvianen.android.gcm.notification_settings"; - - public GCMModule() { - super(); - instance = this; - if (appStateListener == null) { - appStateListener = new AppStateListener(); - TiApplication.addActivityTransitionListener(appStateListener); - } - - } - - public boolean isInForeground() { - return AppStateListener.oneActivityIsResumed; - } - - @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"); - messageCallback = (KrollFunction) options.get("callback"); - - /* Store notification settings in global Ti.App properties */ - JSONObject json = new JSONObject(notificationSettings); - TiApplication.getInstance().getAppProperties().setString(GCMModule.NOTIFICATION_SETTINGS, json.toString()); - - if (senderId != null) { - GCMRegistrar.register(TiApplication.getInstance(), senderId); - - 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"); - } - } - - @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()); - } - } - - - @Kroll.method - @Kroll.getProperty - public String getRegistrationId() { - Log.d(LCAT, "get registrationId property"); - return GCMRegistrar.getRegistrationId(TiApplication.getInstance()); - } - - - @Kroll.method - 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"); - - if (options.get("success") != null) { - successTopicCallback = (KrollFunction) options.get("success"); - } - if (options.get("error") != null) { - errorTopicCallback = (KrollFunction) options.get("error"); - } - if (options.get("callback") != null) { - topicCallback = (KrollFunction) options.get("callback"); - } - - if (topic == null || !topic.startsWith("/topics/")) { - sendError(errorTopicCallback, "No or invalid topic specified, should start with /topics/"); - } - - 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); - } - } - return null; - } - }.execute(); - } else { - sendError(errorTopicCallback, "No GCM senderId specified; get it from the Google Play Developer Console"); - } - } - - @Kroll.method - public void unsubscribe(final HashMap options) { - // unsubscripe from a topic - final String senderId = (String) options.get("senderId"); - final 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 (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()); - } - return null; - } - }.execute(); - } - } - - 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; - } - } - - @Kroll.method - @Kroll.getProperty - @SuppressWarnings("unchecked") - public KrollDict getLastData() { - Map map = new Gson().fromJson(TiApplication.getInstance().getAppProperties().getString(LAST_DATA, null), Map.class); - return map != null ? new KrollDict(map) : null; - } - - @Kroll.method - public void clearLastData() { - TiApplication.getInstance().getAppProperties().removeProperty(LAST_DATA); - } - - /** - * Cancel a notification by the id given in the payload. - * @param notificationId - */ - @Kroll.method - public void cancelNotificationById(int notificationId) { - try { - NotificationManager notificationManager = (NotificationManager) TiApplication.getInstance().getApplicationContext().getSystemService(TiApplication.NOTIFICATION_SERVICE); - notificationManager.cancel(notificationId); - Log.i(LCAT, "Notification " + notificationId + " cleared successfully"); - } catch (Exception ex) { - Log.e(LCAT, "Cannot cancel notification:" + notificationId + " Error: " + ex.getMessage()); - } - } - - @Kroll.method - @Kroll.getProperty - @SuppressWarnings("unchecked") - public KrollDict getNotificationSettings() { - Log.d(LCAT, "Getting notification settings"); - Map map = new Gson().fromJson(TiApplication.getInstance().getAppProperties().getString(GCMModule.NOTIFICATION_SETTINGS, null), Map.class); - return map != null ? new KrollDict(map) : null; - } - - @Kroll.method - @Kroll.setProperty - @SuppressWarnings("unchecked") - public void setNotificationSettings(Map notificationSettings) { - Log.d(LCAT, "Setting notification settings"); - JSONObject json = new JSONObject(notificationSettings); - TiApplication.getInstance().getAppProperties().setString(GCMModule.NOTIFICATION_SETTINGS, json.toString()); - } - - public void sendSuccess(String registrationId) { - if (successCallback != null) { - HashMap data = new HashMap(); - data.put("success", true); - data.put("registrationId", registrationId); - successCallback.callAsync(getKrollObject(), data); - } - } - - public void sendError(String error) { - sendError(errorCallback, error); - } - - - public void sendError(KrollFunction callback, String error) { - Log.e(LCAT, error); - if (callback != null) { - HashMap data = new HashMap(); - data.put("success", false); - data.put("error", error); - - callback.callAsync(getKrollObject(), data); - } - } - - public void sendMessage(HashMap messageData) { - if (messageCallback != null) { - HashMap data = new HashMap(); - data.put("data", messageData); - data.put("inBackground", !isInForeground()); - - messageCallback.call(getKrollObject(), data); - } else { - Log.e(LCAT, "No callback specified for push notification"); - } - } - - public void sendTopicMessage(HashMap messageData) { - if (topicCallback != null) { - HashMap data = new HashMap(); - data.put("data", messageData); - data.put("inBackground", !isInForeground()); - - topicCallback.call(getKrollObject(), data); - } else { - Log.e(LCAT, "No callback specified for topic subscribe"); - } - } - - @Kroll.onAppCreate - public static void onAppCreate(TiApplication app) { - Log.d(LCAT, "onAppCreate " + app + " (" + (instance != null) + ")"); - } - - @Override - protected void initActivity(Activity activity) { - Log.d(LCAT, "initActivity " + activity + " (" + (instance != null) + ")"); - super.initActivity(activity); - } - - @Override - public void onResume(Activity activity) { - Log.d(LCAT, "onResume " + activity + " (" + (instance != null) + ")"); - super.onResume(activity); - } - - @Override - public void onPause(Activity activity) { - Log.d(LCAT, "onPause " + activity + " (" + (instance != null) + ")"); - super.onPause(activity); - } - - @Override - public void onDestroy(Activity activity) { - Log.d(LCAT, "onDestroy " + activity + " (" + (instance != null) + ")"); - super.onDestroy(activity); - } - - @Override - public void onStart(Activity activity) { - Log.d(LCAT, "onStart " + activity + " (" + (instance != null) + ")"); - super.onStart(activity); - } - - @Override - public void onStop(Activity activity) { - Log.d(LCAT, "onStop " + activity + " (" + (instance != null) + ")"); - super.onStop(activity); - } - - public static GCMModule getInstance() { - return instance; - } -} diff --git a/timodule.xml b/timodule.xml deleted file mode 100644 index 0543d18..0000000 --- a/timodule.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 8a1e47d5eeab7d3bf84c21a9841a2290c852ba7a Mon Sep 17 00:00:00 2001 From: Manojkumar Date: Mon, 17 Oct 2016 20:56:36 +0530 Subject: [PATCH 2/6] Upgraded to 5.4.0.GA (TIMOB-23502) --- .classpath | 6 +++--- README.md | 2 +- android/build.properties | 2 +- android/manifest | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.classpath b/.classpath index 0f66b85..2dee36d 100644 --- a/.classpath +++ b/.classpath @@ -5,9 +5,9 @@ - - - + + + diff --git a/README.md b/README.md index 21bea4e..9cf71b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Read the [documentation](https://github.com/morinel/gcmpush/blob/master/document To build, create a `build.properties` file with the following content: ``` -titanium.platform=/Users/###USER###/Library/Application Support/Titanium/mobilesdk/osx/5.1.2.GA/android +titanium.platform=/Users/###USER###/Library/Application Support/Titanium/mobilesdk/osx/5.4.0.GA/android android.platform=/Users/###USER###/Library/Android/sdk/platforms/android-23 google.apis=/Users/###USER###/Library/Android/sdk/add-ons/addon-google_apis-google-23 android.ndk=/Users/###USER###/Library/Android/ndk diff --git a/android/build.properties b/android/build.properties index 126965f..cd0b1b6 100644 --- a/android/build.properties +++ b/android/build.properties @@ -1,4 +1,4 @@ -titanium.platform=/Users/manojkumar/Library/Application Support/Titanium/mobilesdk/osx/5.1.2.GA/android +titanium.platform=/Users/manojkumar/Library/Application Support/Titanium/mobilesdk/osx/5.4.0.GA/android android.platform=/Users/manojkumar/Library/Android/sdk/platforms/android-23 google.apis=/Applications/android-sdk/add-ons/addon-google_apis-google-23 android.ndk=/Applications/android-ndk-r11b \ No newline at end of file diff --git a/android/manifest b/android/manifest index ce75b28..7e67f16 100644 --- a/android/manifest +++ b/android/manifest @@ -14,5 +14,5 @@ name: Gcm moduleid: nl.vanvianen.android.gcm guid: A2371685-B58E-42E4-8403-DF23A877FF0C platform: android -minsdk: 5.1.2.GA +minsdk: 5.4.0.GA architectures: armeabi armeabi-v7a x86 \ No newline at end of file From 4d49037f15caf975ee97654fca04149133fc2cb8 Mon Sep 17 00:00:00 2001 From: Manojkumar Date: Tue, 18 Oct 2016 00:57:44 +0530 Subject: [PATCH 3/6] - Updated plugin to support both Appc & Titanium CLI - Bump version --- android/manifest | 2 +- plugins/gcmpush/hooks/gcmpush.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/android/manifest b/android/manifest index 7e67f16..fa4fe59 100644 --- a/android/manifest +++ b/android/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 1.7.0 +version: 1.8.0 apiversion: 3 description: Google Cloud Push for Titanium author: Jeroen van Vianen diff --git a/plugins/gcmpush/hooks/gcmpush.js b/plugins/gcmpush/hooks/gcmpush.js index 5c14569..5273504 100644 --- a/plugins/gcmpush/hooks/gcmpush.js +++ b/plugins/gcmpush/hooks/gcmpush.js @@ -7,21 +7,22 @@ var LCAT = "GCMPush", exports.init = function(logger, config, cli, nodeappc) { - var opts = config.appc.opts; + var platform = cli.argv.platform, + projectDir = cli.argv["project-dir"]; /** * delete amazon-device-messaging stub library from libs * ToDo: handle with Ant at module build time */ cli.on("build.pre.compile", function(data, done) { - if (opts.platform === "android") { + if (platform === "android") { var moduleId = "nl.vanvianen.android.gcm", modules = cli.tiapp.modules, modulePath; for (var i in modules) { var module = modules[i]; if (module.id === moduleId) { - modulePath = path.join(opts.projectDir, "modules/android", moduleId, module.version, "lib"); + modulePath = path.join(projectDir, "modules/android", moduleId, module.version, "lib"); break; } } From 706eb9c84f5074338969d67fe563d45661a960c9 Mon Sep 17 00:00:00 2001 From: Manojkumar Date: Fri, 21 Oct 2016 18:02:26 +0530 Subject: [PATCH 4/6] Updated plugin - Titanium CLI doesn't copy the assets to bin/assets (TIMOB-12591), overwritten the behvaviour with plugin --- plugins/gcmpush/hooks/gcmpush.js | 59 +++++++++++++++++++------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/plugins/gcmpush/hooks/gcmpush.js b/plugins/gcmpush/hooks/gcmpush.js index 5273504..1024229 100644 --- a/plugins/gcmpush/hooks/gcmpush.js +++ b/plugins/gcmpush/hooks/gcmpush.js @@ -10,12 +10,13 @@ exports.init = function(logger, config, cli, nodeappc) { var platform = cli.argv.platform, projectDir = cli.argv["project-dir"]; - /** - * delete amazon-device-messaging stub library from libs - * ToDo: handle with Ant at module build time - */ - cli.on("build.pre.compile", function(data, done) { - if (platform === "android") { + if (platform === "android") { + + /** + * delete amazon-device-messaging stub library from libs + * ToDo: handle with Ant at module build time + */ + cli.on("build.pre.compile", function(data, done) { var moduleId = "nl.vanvianen.android.gcm", modules = cli.tiapp.modules, modulePath; @@ -34,21 +35,33 @@ exports.init = function(logger, config, cli, nodeappc) { break; } } - } - done(); - }); - - /** - * Appc cli eliminates custom tags - */ - cli.on("build.android.writeAndroidManifest", function(data, done) { - var manifestFilePath = this.androidManifestFile, - manifest = new AndroidManifest().readFile(manifestFilePath); - - manifest.$("manifest").attr("xmlns:amazon", "http://schemas.amazon.com/apk/res/android"); - manifest.$("application").append(""); - manifest.writeFile(manifestFilePath); - - done(); - }); + done(); + }); + + /** + * TIMOB-12591 + */ + cli.on("build.android.copyResource", function(data, done) { + var apiKeyFile = path.join(projectDir, "build/android/assets/api_key.txt"), + binApiKeyFile = path.join(projectDir, "build/android/bin/assets/api_key.txt"); + if (fs.existsSync(apiKeyFile) && !fs.existsSync(binApiKeyFile)) { + fs.createReadStream(apiKeyFile).pipe(fs.createWriteStream(binApiKeyFile)); + } + done(); + }); + + /** + * Appc cli eliminates custom tags + */ + cli.on("build.android.writeAndroidManifest", function(data, done) { + var manifestFilePath = this.androidManifestFile, + manifest = new AndroidManifest().readFile(manifestFilePath); + + manifest.$("manifest").attr("xmlns:amazon", "http://schemas.amazon.com/apk/res/android"); + manifest.$("application").append(""); + manifest.writeFile(manifestFilePath); + + done(); + }); + } }; From 4fac0c73b988a5b6f59a810d36383cf3b2bb4860 Mon Sep 17 00:00:00 2001 From: Manojkumar Murugesan Date: Fri, 21 Oct 2016 23:54:54 +0530 Subject: [PATCH 5/6] Update index.md --- documentation/index.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/documentation/index.md b/documentation/index.md index 865d662..cb20563 100644 --- a/documentation/index.md +++ b/documentation/index.md @@ -9,7 +9,9 @@ A Titanium module for registering a device with Google Cloud Messaging and handl 1. Send a server push notification with your preferred server-side technology to the registrationId returned while registering your device. 1. The callback you specified will then be called. -In order to configure ADM properly, you must include the plugin as shown below and replace the conent of `api_key.txt` with your api key. +This module does not require any tiapp.xml properties, all configuration is done in Javascript. + +In order to configure ADM properly, you must include this [plugin](https://github.com/morinel/gcmpush/tree/master/plugins/gcmpush) as shown below and replace the conent of [api_key.txt](https://github.com/morinel/gcmpush/blob/master/platform/android/assets/api_key.txt) with your api key. ```xml @@ -18,7 +20,13 @@ In order to configure ADM properly, you must include the plugin as shown below a ``` -This module does not require any tiapp.xml properties, all configuration is done in Javascript. +Then you must install the dependent node modules of this plugin by running `npm install` from plugin's root directory. + +This plugin must know the module version you are using, so you must specify the `version` property on the module tag in `tiapp.xml` + +```xml +nl.vanvianen.android.gcm +``` ## Example server-side code to send a push notification ## From 62946a457838942a708905b1d00860a4b9a24415 Mon Sep 17 00:00:00 2001 From: Manojkumar Murugesan Date: Sat, 22 Oct 2016 00:04:04 +0530 Subject: [PATCH 6/6] Update app.js --- example/app.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/example/app.js b/example/app.js index 6ee1f03..8d2cfa5 100644 --- a/example/app.js +++ b/example/app.js @@ -7,6 +7,14 @@ if (lastData) { gcm.clearLastData(); } +/** + * If you are running on a amazon kindle fire device that supports ADM, + * then this module will automatically initate ADM instead of GCM. + */ +if (gcm.isADMSupported()) { + // write any adm specific code here +} + 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 */