Commit 3e2631f3 authored by Chok's avatar Chok
Browse files

chok: init commit

parent 511add9c
Pipeline #20 failed with stages
in 0 seconds
# Change Log
## 3.0.1
**Fixes:**
* fix(android): add service exported flags w/ intent-filter (#173)
* fix(android): processPushBundle flipped returned value (#160)
* fix: doc typo (#159)
**Chores:**
* chore: correct Bower metadata to MIT license (#166)
* chore(npm): rebuilt package-lock.json & fix audit
**Docs:**
* doc: add 2.x and 3.x to PLATFORM_SUPPORT.md
## 3.0.0
**Overview:**
In this major release, there are many breaking changes to the Android platform. The primary changes in this release are dropping Android Support Library for AndroidX, updating the Google Services Gradle Plugin requirement, refactoring the code to Kotlin, and upgrading all other third-party libraries. See below for more details.
---
**Requirement Changes:**
* Cordova-Android: 9.x
---
**Libraries & Dependencie Updates and Changes:**
* Bumped `me.leolin:ShortcutBadger@1.1.22`
* Bumped `Google Services Gradle Plugin@4.3.8`
* Migrated from **Android Support Library** to **AndroidX**
* Added `androidx.core:core@1.6.+`
---
**Breaking:**
* feat!(android): migrate source code to kotlin w/ refactors (#123)
* kotlin@1.5.20
* add before_compile hookscript for cordova-android 9.x support
* converted all java files to kotlin
* updted target-dir path for kotlin files
* refactored with improved null checks
* removed some duplicated code
* updated logging (more logging & better tagging)¥
* feat!(android): bump platform requirements (#122)
* feat!(android): bump me.leolin:ShortcutBadger@1.1.22 (#121)
* feat!(android): bump Google Services Gradle Plugin@4.3.8 (#120)
* feat!(android): Migrate to AndroidX (#119)
* feat!(android): migrate ASL to AndroidX
* feat!(android): swap framework support-v13 w/ androidx.core
* feat!(android): force AndroidXEnabled to true
* feat!(android): bump androidx.core:core@1.6.+
* doc(android): add androidx core version
**Fixes:**
* fix(docs): update TS type import to new package name (#109)
**Chores:**
* chore: rebuilt package-lock.json (#131)
* chore: bump npm dev dependencies (#132) (#133)
* chore(npm): bump devDep, rebuild package-lock & fix audit (#110)
* chore(npm): bump devDep @cordova/eslint-config@^4.0.0 w/ fixes (#144)
**CI:**
* ci: remove old travis configs (#128)
* ci: add codacy badge (#129)
* ci: add gh-actions badge to readme (#130)
**Docs:**
* doc: fixed minor typo (#98)
## 2.0.0
**Overview:**
This release contains breaking changes. One of these needed changes resolved issues of restoring the plugin from npm.
With this breaking change, the `havesource-cordova-plugin-push` package name is no longer used. Please completely uninstall the old version before installing the new version. This will ensure that the correct package name `@havesource/cordova-plugin-push` is used.
There is also an update to the installation requirements:
| | Version |
| - | - |
| Cordova CLI | 10.0.0 |
| Cordova Android | 8.0.0 |
| **Cordova iOS** | **6.0.0** |
| CocoaPods | 1.8.0 |
**Breaking:**
* breaking(ios): requirement bump [#80](https://github.com/havesource/cordova-plugin-push/pull/80)
* breaking: fixed incorrect initial cordova-cli requirement [79333b2](https://github.com/havesource/cordova-plugin-push/commit/79333b25e1ff68fea377be499da91528c82fa21f)
**Feature:**
* feat(ios): force `cocoapods` cdn [#48](https://github.com/havesource/cordova-plugin-push/pull/48)
* feat(ios): support `firebase/messaging` dep version override [#47](https://github.com/havesource/cordova-plugin-push/pull/47)
**Chore:**
* chore(`npm`): rebuilt `package-lock.json` [67e4e4b](https://github.com/havesource/cordova-plugin-push/commit/67e4e4ba185511e60b4d85cae882c41dae1c9cc0)
* chore(`android`): remove duplicate code [#81](https://github.com/havesource/cordova-plugin-push/pull/81)
* chore: bump dev dependencies [#79](https://github.com/havesource/cordova-plugin-push/pull/79)
**CI & Docs:**
* ci(gh-actions): bump dependencies [#78](https://github.com/havesource/cordova-plugin-push/pull/78)
## 1.0.0
**Breaking:**
* breaking(android): bump fcm@18.+ [#19](https://github.com/havesource/cordova-plugin-push/pull/19)
* breaking(android): drop phonegap-plugin-multidex dependency [#21](https://github.com/havesource/cordova-plugin-push/pull/21)
* breaking(android): move clearAllNotifications to destroy from pause [#13](https://github.com/havesource/cordova-plugin-push/pull/13)
**Feature:**
* feat(android): notification data pass [#31](https://github.com/havesource/cordova-plugin-push/pull/31)
* feat(ios): support critical alert notifications [#12](https://github.com/havesource/cordova-plugin-push/pull/12)
* feat(ios): increase firebase framework to 6.32.2 [#42](https://github.com/havesource/cordova-plugin-push/pull/42)
* feat: remove cordova-support-google-services dependency [#8](https://github.com/havesource/cordova-plugin-push/pull/8)
**Fix:**
* fix(android): missing channel description crash [#53](https://github.com/havesource/cordova-plugin-push/pull/53)
* fix(android): Use app name for default channel [#49](https://github.com/havesource/cordova-plugin-push/pull/49)
* fix(android): enable lights when lightColor is set [#15](https://github.com/havesource/cordova-plugin-push/pull/15)
* fix(browser): corrected path to manifest file. [#32](https://github.com/havesource/cordova-plugin-push/pull/32)
**Chore:**
* chore(android): set requirement >= 8.0.0 [#52](https://github.com/havesource/cordova-plugin-push/pull/52)
* chore(android): cleanup & format [#26](https://github.com/havesource/cordova-plugin-push/pull/26)
* chore(android): bump com.android.support:support-v13:28.0.0 [#20](https://github.com/havesource/cordova-plugin-push/pull/20)
* chore(ios): use latest firebase library [#24](https://github.com/havesource/cordova-plugin-push/pull/24)
* chore(npm): rebuilt package-lock.json [#55](https://github.com/havesource/cordova-plugin-push/pull/55)
* chore(npm): properly configure for scope package [#33](https://github.com/havesource/cordova-plugin-push/pull/33)
* chore(type-definition): Update PushNotificationStatic [#14](https://github.com/havesource/cordova-plugin-push/pull/14)
* chore(github-pages): remove config [#4](https://github.com/havesource/cordova-plugin-push/pull/4)
* chore: update ticket management [#27](https://github.com/havesource/cordova-plugin-push/pull/27)
* chore: add missing build of push.js [#22](https://github.com/havesource/cordova-plugin-push/pull/22)
* chore: match plugin.xml version w/ package.json [#10](https://github.com/havesource/cordova-plugin-push/pull/10)
* chore: update xml namespace [#9](https://github.com/havesource/cordova-plugin-push/pull/9)
* chore: update version requirements [#7](https://github.com/havesource/cordova-plugin-push/pull/7)
* chore: update npm & git ignore list [#6](https://github.com/havesource/cordova-plugin-push/pull/6)
* chore: update plugin package [#1](https://github.com/havesource/cordova-plugin-push/pull/1)
* chore: remove unused dependencies [#2](https://github.com/havesource/cordova-plugin-push/pull/2)
**Refactor & Style:**
* refactor(eslint): update dependencies w/ fixes [#3](https://github.com/havesource/cordova-plugin-push/pull/3)
* style(md): format with md all in one (vscode) [#11](https://github.com/havesource/cordova-plugin-push/pull/11)
**CI & Docs:**
* ci(gh-actions): add workflow [#23](https://github.com/havesource/cordova-plugin-push/pull/23)
* ci: update travis configs [#5](https://github.com/havesource/cordova-plugin-push/pull/5)
* doc(android): enable & set notification light with lightColor [#54](https://github.com/havesource/cordova-plugin-push/pull/54)
* doc: cleanup docs [#51](https://github.com/havesource/cordova-plugin-push/pull/51)
* doc: update various markdown docs [#28](https://github.com/havesource/cordova-plugin-push/pull/28)
## Previous Change Log
Since this repo is a fork from the original Adobe PhoneGap push plugin, you can continue to read the previous changelog here:
https://github.com/havesource/cordova-plugin-push/blob/phonegap-2.3.0/CHANGELOG.md
Copyright 2012-2017 Adobe Systems
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Cordova Plugin Push
[![Node CI](https://github.com/havesource/cordova-plugin-push/actions/workflows/ci.yml/badge.svg)](https://github.com/havesource/cordova-plugin-push/actions/workflows/ci.yml) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/422c67b5e70c4a0eadae7b9fc794d3c1)](https://app.codacy.com/gh/havesource/cordova-plugin-push?utm_source=github.com&utm_medium=referral&utm_content=havesource/cordova-plugin-push&utm_campaign=Badge_Grade_Settings)
> Register and receive push notifications
# What is this?
This plugin offers support to receive and handle native push notifications with a **single unified API**.
This does not mean you will be able to send a single push message and have it arrive on devices running different operating systems. By default Android uses FCM and iOS uses APNS and their payloads are significantly different. Even if you are using FCM for both Android and iOS there are differences in the payload required for the plugin to work correctly. For Android **always** put your push payload in the `data` section of the push notification. For more information on why that is the case read [Notification vs Data Payload](https://github.com/havesource/cordova-plugin-push/blob/master/docs/PAYLOAD.md#notification-vs-data-payloads). For iOS follow the regular [FCM documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref).
This plugin does not provide a way to determine which platform you are running on. The best way to do that is use the `device.platform` property provided by [cordova-plugin-device](https://github.com/apache/cordova-plugin-device).
* [Reporting Issues](docs/ISSUES.md)
* [Installation](docs/INSTALLATION.md)
* [API reference](docs/API.md)
* [Typescript support](docs/TYPESCRIPT.md)
* [Examples](docs/EXAMPLES.md)
* [Platform support](docs/PLATFORM_SUPPORT.md)
* [Cloud build support (PG Build, IntelXDK)](docs/PHONEGAP_BUILD.md)
* [Push notification payload details](docs/PAYLOAD.md)
* [Contributing](.github/CONTRIBUTING.md)
* [License (MIT)](MIT-LICENSE)
# Do you like tutorial? You get tutorial!
* [PhoneGap Day US Push Workshop 2016 (using node-gcm)](http://macdonst.github.io/push-workshop/)
# Thanks to all our contributors
[<img alt="10ko" src="https://avatars1.githubusercontent.com/u/2706375?v=4&s=117" width="117">](https://github.com/10ko)[<img alt="TVolly" src="https://avatars3.githubusercontent.com/u/20628284?v=4&s=117" width="117">](https://github.com/TVolly)[<img alt="waptaxi" src="https://avatars2.githubusercontent.com/u/6575569?v=4&s=117" width="117">](https://github.com/waptaxi)[<img alt="viktormuller" src="https://avatars1.githubusercontent.com/u/8171222?v=4&s=117" width="117">](https://github.com/viktormuller)[<img alt="devgeeks" src="https://avatars0.githubusercontent.com/u/554999?v=4&s=117" width="117">](https://github.com/devgeeks)[<img alt="rastafan" src="https://avatars3.githubusercontent.com/u/7632849?v=4&s=117" width="117">](https://github.com/rastafan)
[<img alt="mdoelker" src="https://avatars1.githubusercontent.com/u/2145319?v=4&s=117" width="117">](https://github.com/mdoelker)[<img alt="markeeftb" src="https://avatars3.githubusercontent.com/u/3172570?v=4&s=117" width="117">](https://github.com/markeeftb)[<img alt="malwatte" src="https://avatars2.githubusercontent.com/u/2534778?v=4&s=117" width="117">](https://github.com/malwatte)[<img alt="madebycm" src="https://avatars3.githubusercontent.com/u/5081823?v=4&s=117" width="117">](https://github.com/madebycm)[<img alt="kelvinhokk" src="https://avatars0.githubusercontent.com/u/7766953?v=4&s=117" width="117">](https://github.com/kelvinhokk)[<img alt="keab42" src="https://avatars0.githubusercontent.com/u/1453371?v=4&s=117" width="117">](https://github.com/keab42)
[<img alt="jomarocas" src="https://avatars3.githubusercontent.com/u/5194064?v=4&s=117" width="117">](https://github.com/jomarocas)[<img alt="giuseppelt" src="https://avatars2.githubusercontent.com/u/7147291?v=4&s=117" width="117">](https://github.com/giuseppelt)[<img alt="ericb" src="https://avatars3.githubusercontent.com/u/19638?v=4&s=117" width="117">](https://github.com/ericb)[<img alt="eKazim" src="https://avatars3.githubusercontent.com/u/21195186?v=4&s=117" width="117">](https://github.com/eKazim)[<img alt="clementcontet" src="https://avatars1.githubusercontent.com/u/7261426?v=4&s=117" width="117">](https://github.com/clementcontet)[<img alt="yaswanthsvist" src="https://avatars0.githubusercontent.com/u/3897387?v=4&s=117" width="117">](https://github.com/yaswanthsvist)
[<img alt="Vabs28" src="https://avatars1.githubusercontent.com/u/8149036?v=4&s=117" width="117">](https://github.com/Vabs28)[<img alt="TillaTheHun0" src="https://avatars0.githubusercontent.com/u/8246360?v=4&s=117" width="117">](https://github.com/TillaTheHun0)[<img alt="tomasvarg" src="https://avatars0.githubusercontent.com/u/16196322?v=4&s=117" width="117">](https://github.com/tomasvarg)[<img alt="tobmaster" src="https://avatars1.githubusercontent.com/u/203823?v=4&s=117" width="117">](https://github.com/tobmaster)[<img alt="ThiagoBueno" src="https://avatars1.githubusercontent.com/u/3334244?v=4&s=117" width="117">](https://github.com/ThiagoBueno)[<img alt="szh" src="https://avatars2.githubusercontent.com/u/546965?v=4&s=117" width="117">](https://github.com/szh)
[<img alt="SharUpOff" src="https://avatars3.githubusercontent.com/u/26085497?v=4&s=117" width="117">](https://github.com/SharUpOff)[<img alt="smorstabilini" src="https://avatars0.githubusercontent.com/u/551983?v=4&s=117" width="117">](https://github.com/smorstabilini)[<img alt="fesor" src="https://avatars2.githubusercontent.com/u/172247?v=4&s=117" width="117">](https://github.com/fesor)[<img alt="GreyDekart" src="https://avatars2.githubusercontent.com/u/1793548?v=4&s=117" width="117">](https://github.com/GreyDekart)[<img alt="sebastiansier" src="https://avatars1.githubusercontent.com/u/3733454?v=4&s=117" width="117">](https://github.com/sebastiansier)[<img alt="olastor" src="https://avatars0.githubusercontent.com/u/7479641?v=4&s=117" width="117">](https://github.com/olastor)
[<img alt="tanansatpal" src="https://avatars2.githubusercontent.com/u/20103208?v=4&s=117" width="117">](https://github.com/tanansatpal)[<img alt="SandroGrzicic" src="https://avatars1.githubusercontent.com/u/200247?v=4&s=117" width="117">](https://github.com/SandroGrzicic)[<img alt="xorxor" src="https://avatars1.githubusercontent.com/u/1525525?v=4&s=117" width="117">](https://github.com/xorxor)[<img alt="rubenstolk" src="https://avatars2.githubusercontent.com/u/692644?v=4&s=117" width="117">](https://github.com/rubenstolk)[<img alt="roel-sluper" src="https://avatars0.githubusercontent.com/u/1556404?v=4&s=117" width="117">](https://github.com/roel-sluper)[<img alt="pataar" src="https://avatars3.githubusercontent.com/u/3403851?v=4&s=117" width="117">](https://github.com/pataar)
[<img alt="peteonrails" src="https://avatars1.githubusercontent.com/u/9593?v=4&s=117" width="117">](https://github.com/peteonrails)[<img alt="pjalbuquerque" src="https://avatars3.githubusercontent.com/u/4201558?v=4&s=117" width="117">](https://github.com/pjalbuquerque)[<img alt="NitroGhost" src="https://avatars0.githubusercontent.com/u/3674467?v=4&s=117" width="117">](https://github.com/NitroGhost)[<img alt="matrosov-nikita" src="https://avatars3.githubusercontent.com/u/12752587?v=4&s=117" width="117">](https://github.com/matrosov-nikita)[<img alt="Mikejo5000" src="https://avatars1.githubusercontent.com/u/11948536?v=4&s=117" width="117">](https://github.com/Mikejo5000)[<img alt="michellarcari" src="https://avatars2.githubusercontent.com/u/6429722?v=4&s=117" width="117">](https://github.com/michellarcari)
[<img alt="adamschachne" src="https://avatars2.githubusercontent.com/u/13020251?v=4&s=117" width="117">](https://github.com/adamschachne)[<img alt="alharding" src="https://avatars1.githubusercontent.com/u/926204?v=4&s=117" width="117">](https://github.com/alharding)[<img alt="albertleao" src="https://avatars0.githubusercontent.com/u/1950338?v=4&s=117" width="117">](https://github.com/albertleao)[<img alt="gotev" src="https://avatars0.githubusercontent.com/u/16792495?v=4&s=117" width="117">](https://github.com/gotev)[<img alt="Alex-Sessler" src="https://avatars0.githubusercontent.com/u/5779673?v=4&s=117" width="117">](https://github.com/Alex-Sessler)[<img alt="ben-8409" src="https://avatars1.githubusercontent.com/u/305724?v=4&s=117" width="117">](https://github.com/ben-8409)
[<img alt="bmwertman" src="https://avatars3.githubusercontent.com/u/2573903?v=4&s=117" width="117">](https://github.com/bmwertman)[<img alt="bmatto" src="https://avatars1.githubusercontent.com/u/1044422?v=4&s=117" width="117">](https://github.com/bmatto)[<img alt="countcain" src="https://avatars0.githubusercontent.com/u/1751150?v=4&s=117" width="117">](https://github.com/countcain)[<img alt="CookieCookson" src="https://avatars3.githubusercontent.com/u/3473396?v=4&s=117" width="117">](https://github.com/CookieCookson)[<img alt="cdorner" src="https://avatars3.githubusercontent.com/u/917733?v=4&s=117" width="117">](https://github.com/cdorner)[<img alt="colene" src="https://avatars3.githubusercontent.com/u/1613781?v=4&s=117" width="117">](https://github.com/colene)
[<img alt="cfsnyder" src="https://avatars3.githubusercontent.com/u/3925941?v=4&s=117" width="117">](https://github.com/cfsnyder)[<img alt="cmalard" src="https://avatars0.githubusercontent.com/u/1692136?v=4&s=117" width="117">](https://github.com/cmalard)[<img alt="dansumption" src="https://avatars2.githubusercontent.com/u/174105?v=4&s=117" width="117">](https://github.com/dansumption)[<img alt="dannywillems" src="https://avatars2.githubusercontent.com/u/6018454?v=4&s=117" width="117">](https://github.com/dannywillems)[<img alt="DrMoriarty" src="https://avatars1.githubusercontent.com/u/1177068?v=4&s=117" width="117">](https://github.com/DrMoriarty)[<img alt="eladmoshe" src="https://avatars1.githubusercontent.com/u/1702227?v=4&s=117" width="117">](https://github.com/eladmoshe)
[<img alt="mlabarca" src="https://avatars2.githubusercontent.com/u/4587965?v=4&s=117" width="117">](https://github.com/mlabarca)[<img alt="bromeostasis" src="https://avatars3.githubusercontent.com/u/3764641?v=4&s=117" width="117">](https://github.com/bromeostasis)[<img alt="filmaj" src="https://avatars0.githubusercontent.com/u/52645?v=4&s=117" width="117">](https://github.com/filmaj)[<img alt="geo242" src="https://avatars3.githubusercontent.com/u/7529238?v=4&s=117" width="117">](https://github.com/geo242)[<img alt="gbenvenuti" src="https://avatars3.githubusercontent.com/u/331314?v=4&s=117" width="117">](https://github.com/gbenvenuti)[<img alt="polyn0m" src="https://avatars2.githubusercontent.com/u/1258130?v=4&s=117" width="117">](https://github.com/polyn0m)
[<img alt="jacquesdev" src="https://avatars1.githubusercontent.com/u/7842197?v=4&s=117" width="117">](https://github.com/jacquesdev)[<img alt="janpio" src="https://avatars0.githubusercontent.com/u/183673?v=4&s=117" width="117">](https://github.com/janpio)[<img alt="jakari" src="https://avatars2.githubusercontent.com/u/2283862?v=4&s=117" width="117">](https://github.com/jakari)[<img alt="purplecabbage" src="https://avatars3.githubusercontent.com/u/46134?v=4&s=117" width="117">](https://github.com/purplecabbage)[<img alt="theaccordance" src="https://avatars3.githubusercontent.com/u/1813001?v=4&s=117" width="117">](https://github.com/theaccordance)[<img alt="jonas-m-" src="https://avatars3.githubusercontent.com/u/1147572?v=4&s=117" width="117">](https://github.com/jonas-m-)
[<img alt="Chuckytuh" src="https://avatars3.githubusercontent.com/u/1127199?v=4&s=117" width="117">](https://github.com/Chuckytuh)[<img alt="leonardobazico" src="https://avatars1.githubusercontent.com/u/5280179?v=4&s=117" width="117">](https://github.com/leonardobazico)[<img alt="loslislo-lshift" src="https://avatars0.githubusercontent.com/u/17316151?v=4&s=117" width="117">](https://github.com/loslislo-lshift)[<img alt="luka5" src="https://avatars2.githubusercontent.com/u/1176296?v=4&s=117" width="117">](https://github.com/luka5)[<img alt="mac89" src="https://avatars3.githubusercontent.com/u/2988607?v=4&s=117" width="117">](https://github.com/mac89)[<img alt="markokeeffe" src="https://avatars2.githubusercontent.com/u/1211393?v=4&s=117" width="117">](https://github.com/markokeeffe)
[<img alt="mbektchiev" src="https://avatars1.githubusercontent.com/u/5744783?v=4&s=117" width="117">](https://github.com/mbektchiev)[<img alt="goya" src="https://avatars1.githubusercontent.com/u/208774?v=4&s=117" width="117">](https://github.com/goya)[<img alt="slorber" src="https://avatars0.githubusercontent.com/u/749374?v=4&s=117" width="117">](https://github.com/slorber)[<img alt="daserge" src="https://avatars1.githubusercontent.com/u/4272078?v=4&s=117" width="117">](https://github.com/daserge)[<img alt="smdvdsn" src="https://avatars2.githubusercontent.com/u/507093?v=4&s=117" width="117">](https://github.com/smdvdsn)[<img alt="ryanluker" src="https://avatars2.githubusercontent.com/u/1335972?v=4&s=117" width="117">](https://github.com/ryanluker)
[<img alt="russellbeattie" src="https://avatars1.githubusercontent.com/u/166835?v=4&s=117" width="117">](https://github.com/russellbeattie)[<img alt="rjmunro" src="https://avatars0.githubusercontent.com/u/108641?v=4&s=117" width="117">](https://github.com/rjmunro)[<img alt="hanicker" src="https://avatars3.githubusercontent.com/u/510258?v=4&s=117" width="117">](https://github.com/hanicker)[<img alt="mwbrooks" src="https://avatars1.githubusercontent.com/u/21328?v=4&s=117" width="117">](https://github.com/mwbrooks)[<img alt="LightZam" src="https://avatars3.githubusercontent.com/u/5077142?v=4&s=117" width="117">](https://github.com/LightZam)[<img alt="laagland" src="https://avatars3.githubusercontent.com/u/7661210?v=4&s=117" width="117">](https://github.com/laagland)
[<img alt="cuatl" src="https://avatars1.githubusercontent.com/u/1399392?v=4&s=117" width="117">](https://github.com/cuatl)[<img alt="gianpaj" src="https://avatars3.githubusercontent.com/u/899175?v=4&s=117" width="117">](https://github.com/gianpaj)[<img alt="EdMcBane" src="https://avatars1.githubusercontent.com/u/8511142?v=4&s=117" width="117">](https://github.com/EdMcBane)[<img alt="chriswiggins" src="https://avatars0.githubusercontent.com/u/2830609?v=4&s=117" width="117">](https://github.com/chriswiggins)[<img alt="barryvdh" src="https://avatars2.githubusercontent.com/u/973269?v=4&s=117" width="117">](https://github.com/barryvdh)[<img alt="armno" src="https://avatars3.githubusercontent.com/u/911894?v=4&s=117" width="117">](https://github.com/armno)
[<img alt="archananaik" src="https://avatars2.githubusercontent.com/u/5604248?v=4&s=117" width="117">](https://github.com/archananaik)[<img alt="jakub-g" src="https://avatars2.githubusercontent.com/u/1437027?v=4&s=117" width="117">](https://github.com/jakub-g)[<img alt="shazron" src="https://avatars0.githubusercontent.com/u/36107?v=4&s=117" width="117">](https://github.com/shazron)[<img alt="sclement41" src="https://avatars0.githubusercontent.com/u/443136?v=4&s=117" width="117">](https://github.com/sclement41)[<img alt="hung-doan" src="https://avatars1.githubusercontent.com/u/11371581?v=4&s=117" width="117">](https://github.com/hung-doan)[<img alt="BBosman" src="https://avatars3.githubusercontent.com/u/5115488?v=4&s=117" width="117">](https://github.com/BBosman)
[<img alt="giordanocardillo" src="https://avatars3.githubusercontent.com/u/3403386?v=4&s=117" width="117">](https://github.com/giordanocardillo)[<img alt="mikepsinn" src="https://avatars3.githubusercontent.com/u/2808553?v=4&s=117" width="117">](https://github.com/mikepsinn)[<img alt="AdriVanHoudt" src="https://avatars1.githubusercontent.com/u/2361826?v=4&s=117" width="117">](https://github.com/AdriVanHoudt)[<img alt="alexislg2" src="https://avatars1.githubusercontent.com/u/7933080?v=4&s=117" width="117">](https://github.com/alexislg2)[<img alt="jcesarmobile" src="https://avatars3.githubusercontent.com/u/1637892?v=4&s=117" width="117">](https://github.com/jcesarmobile)[<img alt="nadyaA" src="https://avatars2.githubusercontent.com/u/6064810?v=4&s=117" width="117">](https://github.com/nadyaA)
[<img alt="jdhiro" src="https://avatars0.githubusercontent.com/u/2919453?v=4&s=117" width="117">](https://github.com/jdhiro)[<img alt="edewit" src="https://avatars1.githubusercontent.com/u/51133?v=4&s=117" width="117">](https://github.com/edewit)[<img alt="wildabeast" src="https://avatars1.githubusercontent.com/u/118985?v=4&s=117" width="117">](https://github.com/wildabeast)[<img alt="mkuklis" src="https://avatars2.githubusercontent.com/u/63545?v=4&s=117" width="117">](https://github.com/mkuklis)[<img alt="ashconnell" src="https://avatars2.githubusercontent.com/u/760516?v=4&s=117" width="117">](https://github.com/ashconnell)[<img alt="zwacky" src="https://avatars1.githubusercontent.com/u/1093032?v=4&s=117" width="117">](https://github.com/zwacky)
[<img alt="rakatyal" src="https://avatars2.githubusercontent.com/u/12533467?v=4&s=117" width="117">](https://github.com/rakatyal)[<img alt="jtbdevelopment" src="https://avatars3.githubusercontent.com/u/2074134?v=4&s=117" width="117">](https://github.com/jtbdevelopment)[<img alt="EddyVerbruggen" src="https://avatars1.githubusercontent.com/u/1426370?v=4&s=117" width="117">](https://github.com/EddyVerbruggen)[<img alt="fredgalvao" src="https://avatars2.githubusercontent.com/u/616464?v=4&s=117" width="117">](https://github.com/fredgalvao)[<img alt="bobeast" src="https://avatars0.githubusercontent.com/u/441403?v=4&s=117" width="117">](https://github.com/bobeast)[<img alt="macdonst" src="https://avatars1.githubusercontent.com/u/353180?v=4&s=117" width="117">](https://github.com/macdonst)
[<img alt="larrybahr" src="https://avatars1.githubusercontent.com/u/8782684?v=4&s=117" width="117">](https://github.com/larrybahr)
# Cordova Plugin Push
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin http://dev.silverlakemobility.com/gitlab/cordova-custom/cordova-plugin-push.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](http://dev.silverlakemobility.com/gitlab/cordova-custom/cordova-plugin-push/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
const { join } = require('path');
const { existsSync, readFileSync, writeFileSync } = require('fs');
const { parseElementtreeSync: ParseElementtreeSync } = require('cordova-common/src/util/xml-helpers');
const platform = require('cordova-android');
module.exports = function (context) {
if (!isExecutable()) {
console.log('[cordova-plugin-push::before-compile] skipping before_compile hookscript.');
return;
}
const buildGradleFilePath = join(context.opts.projectRoot, 'platforms/android/build.gradle');
if (!existsSync(buildGradleFilePath)) {
console.log('[cordova-plugin-push::before-compile] could not find "build.gradle" file.');
return;
}
updateBuildGradle(context, buildGradleFilePath);
};
/**
* This hookscript is executable only when the platform version less then 10.x
* @returns Boolean
*/
function isExecutable () {
const majorVersion = parseInt(platform.version(), 10);
return majorVersion < 10 && majorVersion >= 9;
}
function getPluginKotlinVersion (context) {
const pluginConfig = new ParseElementtreeSync(join(context.opts.projectRoot, 'plugins/@havesource/cordova-plugin-push/plugin.xml'));
return pluginConfig
.findall('./platform[@name="android"]').pop()
.findall('./config-file[@target="config.xml"]').pop()
.findall('preference').filter(
elem => elem.attrib.name.toLowerCase() === 'GradlePluginKotlinVersion'.toLowerCase()
).pop().attrib.value;
}
function updateBuildGradle (context, buildGradleFilePath) {
const kotlinVersion = getPluginKotlinVersion(context);
const updateContent = readFileSync(buildGradleFilePath, 'utf8')
.replace(/ext.kotlin_version = ['"](.*)['"]/g, `ext.kotlin_version = '${kotlinVersion}'`);
writeFileSync(buildGradleFilePath, updateContent);
console.log(`[cordova-plugin-push::before-compile] updated "build.gradle" file with kotlin version set to: ${kotlinVersion}`);
}
module.exports = function (context) {
console.log('Updating manifest.json with push properties…');
var path = require('path');
var fs = require('fs');
var platformProjPath = path.join(
context.opts.projectRoot,
'platforms/browser'
);
if (!fs.existsSync(platformProjPath)) {
platformProjPath = context.opts.projectRoot;
}
var platformManifestJson = path.join(platformProjPath, 'www/manifest.json');
if (!fs.existsSync(platformManifestJson)) {
return;
}
fs.readFile(platformManifestJson, 'utf8', function (err, platformJson) {
if (err) throw err; // we'll not consider error handling for now
var platformManifest = JSON.parse(platformJson);
var pluginManifestPath = path.join(
context.opts.projectRoot,
'plugins/@havesource/cordova-plugin-push/src/browser/manifest.json'
);
fs.readFile(pluginManifestPath, 'utf8', function (err, pluginJson) {
if (err) throw err; // we'll not consider error handling for now
var pluginManifest = JSON.parse(pluginJson);
platformManifest.gcm_sender_id = pluginManifest.gcm_sender_id;
fs.writeFile(
platformManifestJson,
JSON.stringify(platformManifest),
function (err) {
if (err) {
return console.log(err);
}
console.log('Manifest updated with push sender ID');
}
);
});
});
};
module.exports = function (context) {
console.log('Updating appxmanifests with ToastCapable=true ...');
var path = require('path');
var fs = require('fs');
var platformProjPath = path.join(context.opts.projectRoot, 'platforms/windows');
if (!fs.existsSync(platformProjPath)) {
platformProjPath = context.opts.projectRoot;
}
var AppxManifest = require(path.join(platformProjPath, 'cordova/lib/AppxManifest'));
['package.phone.appxmanifest', 'package.windows.appxmanifest'].forEach(function (manifestPath) {
var manifest = AppxManifest.get(path.join(platformProjPath, manifestPath));
manifest.getVisualElements().setToastCapable(true);
manifest.write();
});
};
{
"_from": "git+https://github.com/havesource/cordova-plugin-push.git#master",
"_id": "@havesource/cordova-plugin-push@4.0.0-dev.0",
"_inBundle": false,
"_integrity": "",
"_location": "/@havesource/cordova-plugin-push",
"_phantomChildren": {},
"_requested": {
"type": "git",
"raw": "https://github.com/havesource/cordova-plugin-push#master",
"rawSpec": "https://github.com/havesource/cordova-plugin-push#master",
"saveSpec": "git+https://github.com/havesource/cordova-plugin-push.git#master",
"fetchSpec": "https://github.com/havesource/cordova-plugin-push.git",
"gitCommittish": "master"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "git+https://github.com/havesource/cordova-plugin-push.git#c3fb5b894afe17a05e21be135961f5831bafb1e0",
"_spec": "https://github.com/havesource/cordova-plugin-push#master",
"_where": "/Users/n5648/Assorted Project/GerSg3030R3S4-CordovaAndroid11",
"author": {
"name": "Erisu"
},
"bugs": {
"url": "https://github.com/havesource/cordova-plugin-push/issues"
},
"bundleDependencies": false,
"cordova": {
"id": "@havesource/cordova-plugin-push",
"platforms": [
"ios",
"android",
"windows",
"browser"
]
},
"deprecated": false,
"description": "Register and receive push notifications.",
"devDependencies": {
"@babel/cli": "^7.15.4",
"@babel/core": "^7.15.5",
"@babel/eslint-parser": "^7.15.4",
"@babel/preset-env": "^7.15.6",
"@cordova/eslint-config": "^4.0.0",
"babel-plugin-add-header-comment": "^1.0.3",
"fs-extra": "^10.0.0",
"jasmine": "^3.9.0",
"nodemon": "^2.0.12",
"nopt": "^5.0.0",
"xml2js": "^0.4.23"
},
"engines": {
"cordovaDependencies": {
"1.0.0": {
"cordova": ">=10.0.0",
"cordova-android": ">=8.0.0",
"cordova-ios": ">=5.1.1"
},
"2.0.0": {
"cordova": ">=10.0.0",
"cordova-android": ">=8.0.0",
"cordova-ios": ">=6.0.0"
},
"3.0.0": {
"cordova": ">=10.0.0",
"cordova-android": ">=9.0.0",
"cordova-ios": ">=6.0.0"
}
}
},
"homepage": "https://github.com/havesource/cordova-plugin-push#readme",
"keywords": [
"ecosystem:cordova",
"cordova-ios",
"cordova-android",
"cordova-windows",
"cordova-browser"
],
"license": "MIT",
"name": "@havesource/cordova-plugin-push",
"repository": {
"type": "git",
"url": "git+https://github.com/havesource/cordova-plugin-push.git"
},
"scripts": {
"build": "babel src/js --out-dir www",
"build:watch": "nodemon -w ./src/js -e js -x npm run build",
"jasmine": "jasmine --config=spec/unit.json",
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"precommit-msg": "echo 'Pre-commit checks...' && exit 0",
"test": "npm run build && npm run lint && npm run jasmine"
},
"types": "./types/index.d.ts",
"version": "4.0.0-dev.0"
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="@havesource/cordova-plugin-push" version="4.0.0-dev.0">
<name>Cordova Push Plugin</name>
<description>Enable receiving push notifications on Android, iOS and Windows devices. Android uses Firebase Cloud Messaging. iOS uses Apple APNS Notifications. Windows uses Microsoft WNS Notifications.</description>
<license>MIT</license>
<js-module src="www/push.js" name="PushNotification">
<clobbers target="PushNotification"/>
</js-module>
<engines>
<engine name="cordova" version=">=10.0.0"/>
<engine name="cordova-android" version=">=9.0.0"/>
<engine name="cordova-ios" version=">=6.0.0"/>
</engines>
<platform name="android">
<hook type="before_compile" src="hooks/android/beforeCompile.js"/>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="PushNotification">
<param name="android-package" value="com.adobe.phonegap.push.PushPlugin"/>
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="${applicationId}.permission.PushHandlerActivity"/>
<permission android:name="${applicationId}.permission.PushHandlerActivity" android:protectionLevel="signature"></permission>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity android:name="com.adobe.phonegap.push.PushHandlerActivity" android:exported="true" android:permission="${applicationId}.permission.PushHandlerActivity"/>
<activity android:name="com.adobe.phonegap.push.BackgroundHandlerActivity" android:exported="true" android:permission="${applicationId}.permission.BackgroundHandlerActivity">
<intent-filter>
<action android:name="com.adobe.phonegap.push.background.MESSAGING_EVENT"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<receiver android:name="com.adobe.phonegap.push.BackgroundActionButtonHandler"/>
<receiver android:name="com.adobe.phonegap.push.PushDismissedHandler"/>
<service android:name="com.adobe.phonegap.push.FCMService" android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
</config-file>
<config-file target="config.xml" parent="/*">
<preference name="AndroidXEnabled" value="true" />
<preference name="GradlePluginGoogleServicesEnabled" value="true" />
<preference name="GradlePluginGoogleServicesVersion" value="4.3.8" />
<preference name="GradlePluginKotlinEnabled" value="true" />
<preference name="GradlePluginKotlinVersion" value="1.5.20" />
</config-file>
<preference name="ANDROIDX_CORE_VERSION" default="1.6.+"/>
<preference name="FCM_VERSION" default="23.+"/>
<framework src="androidx.core:core:$ANDROIDX_CORE_VERSION" />
<framework src="me.leolin:ShortcutBadger:1.1.22@aar"/>
<framework src="com.google.firebase:firebase-messaging:$FCM_VERSION"/>
<source-file src="src/android/com/adobe/phonegap/push/FCMService.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/PushConstants.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/PushHandlerActivity.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/BackgroundHandlerActivity.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/PushPlugin.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.kt" target-dir="java/com/adobe/phonegap/push/"/>
<source-file src="src/android/com/adobe/phonegap/push/PushDismissedHandler.kt" target-dir="java/com/adobe/phonegap/push/"/>
</platform>
<platform name="browser">
<js-module src="www/browser/push.js" name="BrowserPush">
<clobbers target="PushNotification"/>
</js-module>
<asset src="src/browser/ServiceWorker.js" target="ServiceWorker.js"/>
<asset src="src/browser/manifest.json" target="manifest.json"/>
<hook type="after_prepare" src="hooks/browser/updateManifest.js"/>
</platform>
<platform name="ios">
<preference name="IOS_FIREBASE_MESSAGING_VERSION" default="~> 8.1.1"/>
<config-file target="config.xml" parent="/*">
<feature name="PushNotification">
<param name="ios-package" value="PushPlugin"/>
</feature>
</config-file>
<config-file target="*-Info.plist" parent="UIBackgroundModes">
<array>
<string>remote-notification</string>
</array>
</config-file>
<config-file target="*-Debug.plist" parent="aps-environment">
<string>development</string>
</config-file>
<config-file target="*-Release.plist" parent="aps-environment">
<string>production</string>
</config-file>
<source-file src="src/ios/AppDelegate+notification.m"/>
<source-file src="src/ios/PushPlugin.m"/>
<header-file src="src/ios/AppDelegate+notification.h"/>
<header-file src="src/ios/PushPlugin.h"/>
<framework src="PushKit.framework"/>
<podspec>
<config>
<source url="https://cdn.cocoapods.org/"/>
</config>
<pods use-frameworks="true">
<pod name="Firebase/Messaging" spec="$IOS_FIREBASE_MESSAGING_VERSION" />
</pods>
</podspec>
</platform>
<platform name="windows">
<hook type="after_plugin_install" src="hooks/windows/setToastCapable.js"/>
<js-module src="src/windows/PushPluginProxy.js" name="PushPlugin">
<runs/>
</js-module>
<config-file target="config.xml" parent="/*">
<preference name="WindowsToastCapable" value="true"/>
</config-file>
</platform>
</plugin>
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.RemoteInput
/**
* Background Action Button Handler
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("LongLogTag", "LogConditional")
class BackgroundActionButtonHandler : BroadcastReceiver() {
companion object {
private const val TAG: String = "${PushPlugin.PREFIX_TAG} (BackgroundActionButtonHandler)"
}
/**
* @param context
* @param intent
*/
override fun onReceive(context: Context, intent: Intent) {
val notId = intent.getIntExtra(PushConstants.NOT_ID, 0)
Log.d(TAG, "Not ID: $notId")
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(FCMService.getAppName(context), notId)
intent.extras?.let { extras ->
Log.d(TAG, "Intent Extras: $extras")
extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
putBoolean(PushConstants.FOREGROUND, false)
putBoolean(PushConstants.COLDSTART, false)
putString(
PushConstants.ACTION_CALLBACK,
extras.getString(PushConstants.CALLBACK)
)
RemoteInput.getResultsFromIntent(intent)?.let { remoteInputResults ->
val results = remoteInputResults.getCharSequence(PushConstants.INLINE_REPLY).toString()
Log.d(TAG, "Inline Reply: $results")
putString(PushConstants.INLINE_REPLY, results)
}
}
PushPlugin.sendExtras(extras)
}
}
}
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationManager
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.core.app.RemoteInput
/**
* Background Handler Activity
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("LongLogTag", "LogConditional")
class BackgroundHandlerActivity : Activity() {
companion object {
private const val TAG: String = "${PushPlugin.PREFIX_TAG} (BackgroundHandlerActivity)"
}
/**
* This activity will be started if the user touches a notification that we own.
* We send it's data off to the push plugin for processing.
* If needed, we boot up the main activity to kickstart the application.
*
* @param savedInstanceState
*
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
intent.extras?.let { extras ->
val notId = extras.getInt(PushConstants.NOT_ID, 0)
val callback = extras.getString(PushConstants.CALLBACK)
val startOnBackground = extras.getBoolean(PushConstants.START_IN_BACKGROUND, false)
val dismissed = extras.getBoolean(PushConstants.DISMISSED, false)
Log.d(TAG, "Not ID: $notId")
Log.d(TAG, "Callback: $callback")
Log.d(TAG, "Start In Background: $startOnBackground")
Log.d(TAG, "Dismissed: $dismissed")
FCMService().setNotification(notId, "")
if (!startOnBackground) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(FCMService.getAppName(this), notId)
}
processPushBundle()
finish()
if (!dismissed) {
// Tap the notification, app should start.
if (!PushPlugin.isActive) {
forceMainActivityReload(false)
} else {
forceMainActivityReload(true)
}
}
}
}
private fun processPushBundle() {
/*
* Takes the pushBundle extras from the intent,
* and sends it through to the PushPlugin for processing.
*/
intent.extras?.let { extras ->
var originalExtras = extras.getBundle(PushConstants.PUSH_BUNDLE)
if (originalExtras == null) {
originalExtras = extras
originalExtras.remove(PushConstants.FROM)
originalExtras.remove(PushConstants.MESSAGE_ID)
originalExtras.remove(PushConstants.COLLAPSE_KEY)
}
originalExtras.putBoolean(PushConstants.FOREGROUND, false)
originalExtras.putBoolean(PushConstants.COLDSTART, !PushPlugin.isActive)
originalExtras.putBoolean(PushConstants.DISMISSED, extras.getBoolean(PushConstants.DISMISSED))
originalExtras.putString(
PushConstants.ACTION_CALLBACK,
extras.getString(PushConstants.CALLBACK)
)
originalExtras.remove(PushConstants.NO_CACHE)
RemoteInput.getResultsFromIntent(intent)?.apply {
val reply = getCharSequence(PushConstants.INLINE_REPLY).toString()
Log.d(TAG, "Inline Reply: $reply")
originalExtras.putString(PushConstants.INLINE_REPLY, reply)
}
PushPlugin.sendExtras(originalExtras)
}
}
private fun forceMainActivityReload(startOnBackground: Boolean) {
/*
* Forces the main activity to re-launch if it's unloaded.
*/
val launchIntent = packageManager.getLaunchIntentForPackage(applicationContext.packageName)
intent.extras?.let { extras ->
launchIntent?.apply {
extras.getBundle(PushConstants.PUSH_BUNDLE)?.let { originalExtras ->
putExtras(originalExtras)
}
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_FROM_BACKGROUND)
putExtra(PushConstants.START_IN_BACKGROUND, startOnBackground)
}
}
startActivity(launchIntent)
}
/**
*
*/
override fun onResume() {
super.onResume()
val notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}
}
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.*
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.text.Html
import android.text.Spanned
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
import com.adobe.phonegap.push.PushPlugin.Companion.isActive
import com.adobe.phonegap.push.PushPlugin.Companion.isInForeground
import com.adobe.phonegap.push.PushPlugin.Companion.sendExtras
import com.adobe.phonegap.push.PushPlugin.Companion.setApplicationIconBadgeNumber
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.security.SecureRandom
import java.util.*
/**
* Firebase Cloud Messaging Service Class
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("NewApi", "LongLogTag", "LogConditional")
class FCMService : FirebaseMessagingService() {
companion object {
private const val TAG = "${PushPlugin.PREFIX_TAG} (FCMService)"
private val messageMap = HashMap<Int, ArrayList<String?>>()
private val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_MUTABLE
} else {
0
}
private val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
/**
* Get the Application Name from Label
*/
fun getAppName(context: Context): String {
return context.packageManager.getApplicationLabel(context.applicationInfo) as String
}
}
private val context: Context
get() = applicationContext
private val pushSharedPref: SharedPreferences
get() = context.getSharedPreferences(
PushConstants.COM_ADOBE_PHONEGAP_PUSH,
MODE_PRIVATE
)
/**
* Called when a new token is generated, after app install or token changes.
*
* @param token
*/
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d(TAG, "Refreshed token: $token")
// TODO: Implement this method to send any registration to your app's servers.
//sendRegistrationToServer(token);
}
/**
* Set Notification
* If message is empty or null, the message list is cleared.
*
* @param notId
* @param message
*/
fun setNotification(notId: Int, message: String?) {
var messageList = messageMap[notId]
if (messageList == null) {
messageList = ArrayList()
messageMap[notId] = messageList
}
if (message == null || message.isEmpty()) {
messageList.clear()
} else {
messageList.add(message)
}
}
/**
* On Message Received
*/
override fun onMessageReceived(message: RemoteMessage) {
val from = message.from
Log.d(TAG, "onMessageReceived (from=$from)")
var extras = Bundle()
message.notification?.let {
extras.putString(PushConstants.TITLE, it.title)
extras.putString(PushConstants.MESSAGE, it.body)
extras.putString(PushConstants.SOUND, it.sound)
extras.putString(PushConstants.ICON, it.icon)
extras.putString(PushConstants.COLOR, it.color)
}
for ((key, value) in message.data) {
extras.putString(key, value)
}
if (isAvailableSender(from)) {
val messageKey = pushSharedPref.getString(PushConstants.MESSAGE_KEY, PushConstants.MESSAGE)
val titleKey = pushSharedPref.getString(PushConstants.TITLE_KEY, PushConstants.TITLE)
extras = normalizeExtras(extras, messageKey, titleKey)
// Clear Badge
val clearBadge = pushSharedPref.getBoolean(PushConstants.CLEAR_BADGE, false)
if (clearBadge) {
setApplicationIconBadgeNumber(context, 0)
}
// Foreground
extras.putBoolean(PushConstants.FOREGROUND, isInForeground)
// if we are in the foreground and forceShow is `false` only send data
val forceShow = pushSharedPref.getBoolean(PushConstants.FORCE_SHOW, false)
if (!forceShow && isInForeground) {
Log.d(TAG, "Do Not Force & Is In Foreground")
extras.putBoolean(PushConstants.COLDSTART, false)
sendExtras(extras)
} else if (forceShow && isInForeground) {
Log.d(TAG, "Force & Is In Foreground")
extras.putBoolean(PushConstants.COLDSTART, false)
showNotificationIfPossible(extras)
} else {
Log.d(TAG, "In Background")
extras.putBoolean(PushConstants.COLDSTART, isActive)
showNotificationIfPossible(extras)
}
}
}
private fun replaceKey(oldKey: String, newKey: String, extras: Bundle, newExtras: Bundle) {
/*
* Change a values key in the extras bundle
*/
var value = extras[oldKey]
if (value != null) {
when (value) {
is String -> {
value = localizeKey(newKey, value)
newExtras.putString(newKey, value as String?)
}
is Boolean -> newExtras.putBoolean(newKey, (value as Boolean?) ?: return)
is Number -> {
newExtras.putDouble(newKey, value.toDouble())
}
else -> {
newExtras.putString(newKey, value.toString())
}
}
}
}
private fun localizeKey(key: String, value: String): String {
/*
* Normalize localization for key
*/
return when (key) {
PushConstants.TITLE,
PushConstants.MESSAGE,
PushConstants.SUMMARY_TEXT,
-> {
try {
val localeObject = JSONObject(value)
val localeKey = localeObject.getString(PushConstants.LOC_KEY)
val localeFormatData = ArrayList<String>()
if (!localeObject.isNull(PushConstants.LOC_DATA)) {
val localeData = localeObject.getString(PushConstants.LOC_DATA)
val localeDataArray = JSONArray(localeData)
for (i in 0 until localeDataArray.length()) {
localeFormatData.add(localeDataArray.getString(i))
}
}
val resourceId = context.resources.getIdentifier(
localeKey,
"string",
context.packageName
)
if (resourceId != 0) {
context.resources.getString(resourceId, *localeFormatData.toTypedArray())
} else {
Log.d(TAG, "Can't Find Locale Resource (key=$localeKey)")
value
}
} catch (e: JSONException) {
Log.d(TAG, "No Locale Found (key= $key, error=${e.message})")
value
}
}
else -> value
}
}
private fun normalizeKey(
key: String,
messageKey: String?,
titleKey: String?,
newExtras: Bundle,
): String {
/*
* Replace alternate keys with our canonical value
*/
return when {
key == PushConstants.BODY
|| key == PushConstants.ALERT
|| key == PushConstants.MP_MESSAGE
|| key == PushConstants.GCM_NOTIFICATION_BODY
|| key == PushConstants.TWILIO_BODY
|| key == messageKey
|| key == PushConstants.AWS_PINPOINT_BODY
-> {
PushConstants.MESSAGE
}
key == PushConstants.TWILIO_TITLE || key == PushConstants.SUBJECT || key == titleKey -> {
PushConstants.TITLE
}
key == PushConstants.MSGCNT || key == PushConstants.BADGE -> {
PushConstants.COUNT
}
key == PushConstants.SOUNDNAME || key == PushConstants.TWILIO_SOUND -> {
PushConstants.SOUND
}
key == PushConstants.AWS_PINPOINT_PICTURE -> {
newExtras.putString(PushConstants.STYLE, PushConstants.STYLE_PICTURE)
PushConstants.PICTURE
}
key.startsWith(PushConstants.GCM_NOTIFICATION) -> {
key.substring(PushConstants.GCM_NOTIFICATION.length + 1, key.length)
}
key.startsWith(PushConstants.GCM_N) -> {
key.substring(PushConstants.GCM_N.length + 1, key.length)
}
key.startsWith(PushConstants.UA_PREFIX) -> {
key.substring(PushConstants.UA_PREFIX.length + 1, key.length).lowercase()
}
key.startsWith(PushConstants.AWS_PINPOINT_PREFIX) -> {
key.substring(PushConstants.AWS_PINPOINT_PREFIX.length + 1, key.length)
}
else -> key
}
}
private fun normalizeExtras(
extras: Bundle,
messageKey: String?,
titleKey: String?,
): Bundle {
/*
* Parse bundle into normalized keys.
*/
Log.d(TAG, "normalize extras")
val it: Iterator<String> = extras.keySet().iterator()
val newExtras = Bundle()
while (it.hasNext()) {
val key = it.next()
Log.d(TAG, "key = $key")
// If normalizeKey, the key is "data" or "message" and the value is a json object extract
// This is to support parse.com and other services. Issue #147 and pull #218
if (
key == PushConstants.PARSE_COM_DATA ||
key == PushConstants.MESSAGE ||
key == messageKey
) {
val json = extras[key]
// Make sure data is in json object string format
if (json is String && json.startsWith("{")) {
Log.d(TAG, "extracting nested message data from key = $key")
try {
// If object contains message keys promote each value to the root of the bundle
val data = JSONObject(json)
if (
data.has(PushConstants.ALERT)
|| data.has(PushConstants.MESSAGE)
|| data.has(PushConstants.BODY)
|| data.has(PushConstants.TITLE)
|| data.has(messageKey)
|| data.has(titleKey)
) {
val jsonKeys = data.keys()
while (jsonKeys.hasNext()) {
var jsonKey = jsonKeys.next()
Log.d(TAG, "key = data/$jsonKey")
var value = data.getString(jsonKey)
jsonKey = normalizeKey(jsonKey, messageKey, titleKey, newExtras)
value = localizeKey(jsonKey, value)
newExtras.putString(jsonKey, value)
}
} else if (data.has(PushConstants.LOC_KEY) || data.has(PushConstants.LOC_DATA)) {
val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
Log.d(TAG, "replace key $key with $newKey")
replaceKey(key, newKey, extras, newExtras)
}
} catch (e: JSONException) {
Log.e(TAG, "normalizeExtras: JSON exception")
}
} else {
val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
Log.d(TAG, "replace key $key with $newKey")
replaceKey(key, newKey, extras, newExtras)
}
} else if (key == "notification") {
val value = extras.getBundle(key)
val iterator: Iterator<String> = value!!.keySet().iterator()
while (iterator.hasNext()) {
val notificationKey = iterator.next()
Log.d(TAG, "notificationKey = $notificationKey")
val newKey = normalizeKey(notificationKey, messageKey, titleKey, newExtras)
Log.d(TAG, "Replace key $notificationKey with $newKey")
var valueData = value.getString(notificationKey)
valueData = localizeKey(newKey, valueData!!)
newExtras.putString(newKey, valueData)
}
continue
// In case we weren't working on the payload data node or the notification node,
// normalize the key.
// This allows to have "message" as the payload data key without colliding
// with the other "message" key (holding the body of the payload)
// See issue #1663
} else {
val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
Log.d(TAG, "replace key $key with $newKey")
replaceKey(key, newKey, extras, newExtras)
}
} // while
return newExtras
}
private fun extractBadgeCount(extras: Bundle?): Int {
var count = -1
try {
extras?.getString(PushConstants.COUNT)?.let {
count = it.toInt()
}
} catch (e: NumberFormatException) {
Log.e(TAG, e.localizedMessage, e)
}
return count
}
private fun showNotificationIfPossible(extras: Bundle?) {
// Send a notification if there is a message or title, otherwise just send data
extras?.let {
val message = it.getString(PushConstants.MESSAGE)
val title = it.getString(PushConstants.TITLE)
val contentAvailable = it.getString(PushConstants.CONTENT_AVAILABLE)
val forceStart = it.getString(PushConstants.FORCE_START)
val badgeCount = extractBadgeCount(extras)
if (badgeCount >= 0) {
setApplicationIconBadgeNumber(context, badgeCount)
}
if (badgeCount == 0) {
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mNotificationManager.cancelAll()
}
Log.d(TAG, "message=$message")
Log.d(TAG, "title=$title")
Log.d(TAG, "contentAvailable=$contentAvailable")
Log.d(TAG, "forceStart=$forceStart")
Log.d(TAG, "badgeCount=$badgeCount")
val hasMessage = message != null && message.isNotEmpty()
val hasTitle = title != null && title.isNotEmpty()
if (hasMessage || hasTitle) {
Log.d(TAG, "Create Notification")
if (!hasTitle) {
extras.putString(PushConstants.TITLE, getAppName(this))
}
createNotification(extras)
}
if (!isActive && forceStart == "1") {
Log.d(TAG, "The app is not running, attempting to start in the background")
val intent = Intent(this, PushHandlerActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(PushConstants.PUSH_BUNDLE, extras)
putExtra(PushConstants.START_IN_BACKGROUND, true)
putExtra(PushConstants.FOREGROUND, false)
}
startActivity(intent)
} else if (contentAvailable == "1") {
Log.d(
TAG,
"The app is not running and content available is true, sending notification event"
)
sendExtras(extras)
}
}
}
private fun createNotification(extras: Bundle?) {
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val appName = getAppName(this)
val notId = parseNotificationIdToInt(extras)
val notificationIntent = Intent(this, PushHandlerActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra(PushConstants.PUSH_BUNDLE, extras)
putExtra(PushConstants.NOT_ID, notId)
}
val random = SecureRandom()
var requestCode = random.nextInt()
val contentIntent = PendingIntent.getActivity(
this,
requestCode,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
)
val dismissedNotificationIntent = Intent(
this,
PushDismissedHandler::class.java
).apply {
putExtra(PushConstants.PUSH_BUNDLE, extras)
putExtra(PushConstants.NOT_ID, notId)
putExtra(PushConstants.DISMISSED, true)
action = PushConstants.PUSH_DISMISSED
}
requestCode = random.nextInt()
val deleteIntent = PendingIntent.getBroadcast(
this,
requestCode,
dismissedNotificationIntent,
PendingIntent.FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE
)
val mBuilder: NotificationCompat.Builder =
createNotificationBuilder(extras, mNotificationManager)
mBuilder.setWhen(System.currentTimeMillis())
.setContentTitle(fromHtml(extras?.getString(PushConstants.TITLE)))
.setTicker(fromHtml(extras?.getString(PushConstants.TITLE)))
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setAutoCancel(true)
val localIcon = pushSharedPref.getString(PushConstants.ICON, null)
val localIconColor = pushSharedPref.getString(PushConstants.ICON_COLOR, null)
val soundOption = pushSharedPref.getBoolean(PushConstants.SOUND, true)
val vibrateOption = pushSharedPref.getBoolean(PushConstants.VIBRATE, true)
Log.d(TAG, "stored icon=$localIcon")
Log.d(TAG, "stored iconColor=$localIconColor")
Log.d(TAG, "stored sound=$soundOption")
Log.d(TAG, "stored vibrate=$vibrateOption")
/*
* Notification Vibration
*/
setNotificationVibration(extras, vibrateOption, mBuilder)
/*
* Notification Icon Color
*
* Sets the small-icon background color of the notification.
* To use, add the `iconColor` key to plugin android options
*/
setNotificationIconColor(extras?.getString(PushConstants.COLOR), mBuilder, localIconColor)
/*
* Notification Icon
*
* Sets the small-icon of the notification.
*
* - checks the plugin options for `icon` key
* - if none, uses the application icon
*
* The icon value must be a string that maps to a drawable resource.
* If no resource is found, falls
*/
setNotificationSmallIcon(extras, mBuilder, localIcon)
/*
* Notification Large-Icon
*
* Sets the large-icon of the notification
*
* - checks the gcm data for the `image` key
* - checks to see if remote image, loads it.
* - checks to see if assets image, Loads It.
* - checks to see if resource image, LOADS IT!
* - if none, we don't set the large icon
*/
setNotificationLargeIcon(extras, mBuilder)
/*
* Notification Sound
*/
if (soundOption) {
setNotificationSound(extras, mBuilder)
}
/*
* LED Notification
*/
setNotificationLedColor(extras, mBuilder)
/*
* Priority Notification
*/
setNotificationPriority(extras, mBuilder)
/*
* Notification message
*/
setNotificationMessage(notId, extras, mBuilder)
/*
* Notification count
*/
setNotificationCount(extras, mBuilder)
/*
* Notification ongoing
*/
setNotificationOngoing(extras, mBuilder)
/*
* Notification count
*/
setVisibility(extras, mBuilder)
/*
* Notification add actions
*/
createActions(extras, mBuilder, notId)
mNotificationManager.notify(appName, notId, mBuilder.build())
}
private fun createNotificationBuilder(
extras: Bundle?,
notificationManager: NotificationManager
): NotificationCompat.Builder {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var channelID: String? = null
if (extras != null) {
channelID = extras.getString(PushConstants.ANDROID_CHANNEL_ID)
}
// if the push payload specifies a channel use it
return if (channelID != null) {
NotificationCompat.Builder(context, channelID)
} else {
val channels = notificationManager.notificationChannels
channelID = if (channels.size == 1) {
channels[0].id.toString()
} else {
PushConstants.DEFAULT_CHANNEL_ID
}
Log.d(TAG, "Using channel ID = $channelID")
NotificationCompat.Builder(context, channelID)
}
} else {
return NotificationCompat.Builder(context)
}
}
private fun updateIntent(
intent: Intent,
callback: String,
extras: Bundle?,
foreground: Boolean,
notId: Int,
) {
intent.apply {
putExtra(PushConstants.CALLBACK, callback)
putExtra(PushConstants.PUSH_BUNDLE, extras)
putExtra(PushConstants.FOREGROUND, foreground)
putExtra(PushConstants.NOT_ID, notId)
}
}
private fun createActions(
extras: Bundle?,
mBuilder: NotificationCompat.Builder,
notId: Int,
) {
Log.d(TAG, "create actions: with in-line")
if (extras == null) {
Log.d(TAG, "create actions: extras is null, skipping")
return
}
val actions = extras.getString(PushConstants.ACTIONS)
if (actions != null) {
try {
val actionsArray = JSONArray(actions)
val wActions = ArrayList<NotificationCompat.Action>()
for (i in 0 until actionsArray.length()) {
val min = 1
val max = 2000000000
val random = SecureRandom()
val uniquePendingIntentRequestCode = random.nextInt(max - min + 1) + min
Log.d(TAG, "adding action")
val action = actionsArray.getJSONObject(i)
Log.d(TAG, "adding callback = " + action.getString(PushConstants.CALLBACK))
val foreground = action.optBoolean(PushConstants.FOREGROUND, true)
val inline = action.optBoolean("inline", false)
var intent: Intent?
var pIntent: PendingIntent?
val callback = action.getString(PushConstants.CALLBACK)
when {
inline -> {
Log.d(TAG, "Version: ${Build.VERSION.SDK_INT} = ${Build.VERSION_CODES.M}")
intent = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Log.d(TAG, "Push Activity")
Intent(this, PushHandlerActivity::class.java)
} else {
Log.d(TAG, "Push Receiver")
Intent(this, BackgroundActionButtonHandler::class.java)
}
updateIntent(intent, callback, extras, foreground, notId)
pIntent = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Log.d(TAG, "push activity for notId $notId")
PendingIntent.getActivity(
this,
uniquePendingIntentRequestCode,
intent,
PendingIntent.FLAG_ONE_SHOT or FLAG_MUTABLE
)
} else {
Log.d(TAG, "push receiver for notId $notId")
PendingIntent.getBroadcast(
this,
uniquePendingIntentRequestCode,
intent,
PendingIntent.FLAG_ONE_SHOT or FLAG_MUTABLE
)
}
}
foreground -> {
intent = Intent(this, PushHandlerActivity::class.java)
updateIntent(intent, callback, extras, foreground, notId)
pIntent = PendingIntent.getActivity(
this, uniquePendingIntentRequestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
)
}
else -> {
intent = Intent(this, BackgroundActionButtonHandler::class.java)
updateIntent(intent, callback, extras, foreground, notId)
pIntent = PendingIntent.getBroadcast(
this, uniquePendingIntentRequestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
)
}
}
val actionBuilder = NotificationCompat.Action.Builder(
getImageId(action.optString(PushConstants.ICON, "")),
action.getString(PushConstants.TITLE),
pIntent
)
var remoteInput: RemoteInput?
if (inline) {
Log.d(TAG, "Create Remote Input")
val replyLabel = action.optString(
PushConstants.INLINE_REPLY_LABEL,
"Enter your reply here"
)
remoteInput = RemoteInput.Builder(PushConstants.INLINE_REPLY)
.setLabel(replyLabel)
.build()
actionBuilder.addRemoteInput(remoteInput)
}
val wAction: NotificationCompat.Action = actionBuilder.build()
wActions.add(actionBuilder.build())
if (inline) {
mBuilder.addAction(wAction)
} else {
mBuilder.addAction(
getImageId(action.optString(PushConstants.ICON, "")),
action.getString(PushConstants.TITLE),
pIntent
)
}
}
mBuilder.extend(NotificationCompat.WearableExtender().addActions(wActions))
wActions.clear()
} catch (e: JSONException) {
Log.e(TAG, "JSONException caught", e)
}
}
}
private fun setNotificationCount(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
val count = extractBadgeCount(extras)
if (count >= 0) {
Log.d(TAG, "count =[$count]")
mBuilder.setNumber(count)
}
}
private fun setVisibility(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
extras?.getString(PushConstants.VISIBILITY)?.let { visibilityStr ->
try {
val visibilityInt = visibilityStr.toInt()
if (
visibilityInt >= NotificationCompat.VISIBILITY_SECRET
&& visibilityInt <= NotificationCompat.VISIBILITY_PUBLIC
) {
mBuilder.setVisibility(visibilityInt)
} else {
Log.e(TAG, "Visibility parameter must be between -1 and 1")
}
} catch (e: NumberFormatException) {
}
}
}
private fun setNotificationVibration(
extras: Bundle?,
vibrateOption: Boolean,
mBuilder: NotificationCompat.Builder,
) {
if (extras == null) {
Log.d(TAG, "setNotificationVibration: extras is null, skipping")
return
}
val vibrationPattern = extras.getString(PushConstants.VIBRATION_PATTERN)
if (vibrationPattern != null) {
val items = convertToTypedArray(vibrationPattern)
val results = LongArray(items.size)
for (i in items.indices) {
try {
results[i] = items[i].trim { it <= ' ' }.toLong()
} catch (nfe: NumberFormatException) {
Log.e(TAG, "NumberFormatException caught while parsing vibration pattern", nfe)
}
}
mBuilder.setVibrate(results)
} else {
if (vibrateOption) {
mBuilder.setDefaults(Notification.DEFAULT_VIBRATE)
}
}
}
private fun setNotificationOngoing(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
extras?.getString(PushConstants.ONGOING, "false")?.let {
mBuilder.setOngoing(it.toBoolean())
}
}
private fun setNotificationMessage(
notId: Int,
extras: Bundle?,
mBuilder: NotificationCompat.Builder,
) {
extras?.let {
val message = it.getString(PushConstants.MESSAGE)
when (it.getString(PushConstants.STYLE, PushConstants.STYLE_TEXT)) {
PushConstants.STYLE_INBOX -> {
setNotification(notId, message)
mBuilder.setContentText(fromHtml(message))
messageMap[notId]?.let { messageList ->
val sizeList = messageList.size
if (sizeList > 1) {
val sizeListMessage = sizeList.toString()
var stacking: String? = "$sizeList more"
it.getString(PushConstants.SUMMARY_TEXT)?.let { summaryText ->
stacking = summaryText.replace("%n%", sizeListMessage)
}
val notificationInbox = NotificationCompat.InboxStyle().run {
setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
setSummaryText(fromHtml(stacking))
}.also { inbox ->
for (i in messageList.indices.reversed()) {
inbox.addLine(fromHtml(messageList[i]))
}
}
mBuilder.setStyle(notificationInbox)
} else {
message?.let { message ->
val bigText = NotificationCompat.BigTextStyle().run {
bigText(fromHtml(message))
setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
}
mBuilder.setStyle(bigText)
}
}
}
}
PushConstants.STYLE_PICTURE -> {
setNotification(notId, "")
val bigPicture = NotificationCompat.BigPictureStyle().run {
bigPicture(getBitmapFromURL(it.getString(PushConstants.PICTURE)))
setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
setSummaryText(fromHtml(it.getString(PushConstants.SUMMARY_TEXT)))
}
mBuilder.apply {
setContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
setContentText(fromHtml(message))
setStyle(bigPicture)
}
}
else -> {
setNotification(notId, "")
message?.let { messageStr ->
val bigText = NotificationCompat.BigTextStyle().run {
bigText(fromHtml(messageStr))
setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
it.getString(PushConstants.SUMMARY_TEXT)?.let { summaryText ->
setSummaryText(fromHtml(summaryText))
}
}
mBuilder.setContentText(fromHtml(messageStr))
mBuilder.setStyle(bigText)
}
}
}
}
}
private fun setNotificationSound(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
extras?.let {
val soundName = it.getString(PushConstants.SOUNDNAME) ?: it.getString(PushConstants.SOUND)
when {
soundName == PushConstants.SOUND_RINGTONE -> {
mBuilder.setSound(Settings.System.DEFAULT_RINGTONE_URI)
}
soundName != null && !soundName.contentEquals(PushConstants.SOUND_DEFAULT) -> {
val sound = Uri.parse(
"${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context.packageName}/raw/$soundName"
)
Log.d(TAG, "Sound URL: $sound")
mBuilder.setSound(sound)
}
else -> {
mBuilder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
}
}
}
}
private fun convertToTypedArray(item: String): Array<String> {
return item.replace("\\[".toRegex(), "")
.replace("]".toRegex(), "")
.split(",")
.toTypedArray()
}
private fun setNotificationLedColor(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
extras?.let { it ->
it.getString(PushConstants.LED_COLOR)?.let { ledColor ->
// Convert ledColor to Int Typed Array
val items = convertToTypedArray(ledColor)
val results = IntArray(items.size)
for (i in items.indices) {
try {
results[i] = items[i].trim { it <= ' ' }.toInt()
} catch (nfe: NumberFormatException) {
Log.e(TAG, "Number Format Exception: $nfe")
}
}
if (results.size == 4) {
val (alpha, red, green, blue) = results
mBuilder.setLights(Color.argb(alpha, red, green, blue), 500, 500)
} else {
Log.e(TAG, "ledColor parameter must be an array of length == 4 (ARGB)")
}
}
}
}
private fun setNotificationPriority(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
extras?.let { it ->
it.getString(PushConstants.PRIORITY)?.let { priorityStr ->
try {
val priority = priorityStr.toInt()
if (
priority >= NotificationCompat.PRIORITY_MIN
&& priority <= NotificationCompat.PRIORITY_MAX
) {
mBuilder.priority = priority
} else {
Log.e(TAG, "Priority parameter must be between -2 and 2")
}
} catch (e: NumberFormatException) {
}
}
}
}
private fun getCircleBitmap(bitmap: Bitmap?): Bitmap? {
if (bitmap == null) {
return null
}
val output = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val paint = Paint().apply {
isAntiAlias = true
color = Color.RED
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
}
Canvas(output).apply {
drawARGB(0, 0, 0, 0)
val cx = (bitmap.width / 2).toFloat()
val cy = (bitmap.height / 2).toFloat()
val radius = if (cx < cy) cx else cy
val rect = Rect(0, 0, bitmap.width, bitmap.height)
drawCircle(cx, cy, radius, paint)
drawBitmap(bitmap, rect, rect, paint)
}
bitmap.recycle()
return output
}
private fun setNotificationLargeIcon(
extras: Bundle?,
mBuilder: NotificationCompat.Builder,
) {
extras?.let {
val gcmLargeIcon = it.getString(PushConstants.IMAGE)
val imageType = it.getString(PushConstants.IMAGE_TYPE, PushConstants.IMAGE_TYPE_SQUARE)
if (gcmLargeIcon != null && gcmLargeIcon != "") {
if (
gcmLargeIcon.startsWith("http://")
|| gcmLargeIcon.startsWith("https://")
) {
val bitmap = getBitmapFromURL(gcmLargeIcon)
if (PushConstants.IMAGE_TYPE_SQUARE.equals(imageType, ignoreCase = true)) {
mBuilder.setLargeIcon(bitmap)
} else {
val bm = getCircleBitmap(bitmap)
mBuilder.setLargeIcon(bm)
}
Log.d(TAG, "Using remote large-icon from GCM")
} else {
try {
val inputStream: InputStream = assets.open(gcmLargeIcon)
val bitmap = BitmapFactory.decodeStream(inputStream)
if (PushConstants.IMAGE_TYPE_SQUARE.equals(imageType, ignoreCase = true)) {
mBuilder.setLargeIcon(bitmap)
} else {
val bm = getCircleBitmap(bitmap)
mBuilder.setLargeIcon(bm)
}
Log.d(TAG, "Using assets large-icon from GCM")
} catch (e: IOException) {
val largeIconId: Int = getImageId(gcmLargeIcon)
if (largeIconId != 0) {
val largeIconBitmap = BitmapFactory.decodeResource(context.resources, largeIconId)
mBuilder.setLargeIcon(largeIconBitmap)
Log.d(TAG, "Using resources large-icon from GCM")
} else {
Log.d(TAG, "Not large icon settings")
}
}
}
}
}
}
private fun getImageId(icon: String): Int {
var iconId = context.resources.getIdentifier(icon, PushConstants.DRAWABLE, context.packageName)
if (iconId == 0) {
iconId = context.resources.getIdentifier(icon, "mipmap", context.packageName)
}
return iconId
}
private fun setNotificationSmallIcon(
extras: Bundle?,
mBuilder: NotificationCompat.Builder,
localIcon: String?,
) {
extras?.let {
val icon = it.getString(PushConstants.ICON)
val iconId = when {
icon != null && icon != "" -> {
getImageId(icon)
}
localIcon != null && localIcon != "" -> {
getImageId(localIcon)
}
else -> {
Log.d(TAG, "No icon resource found from settings, using application icon")
context.applicationInfo.icon
}
}
mBuilder.setSmallIcon(iconId)
}
}
private fun setNotificationIconColor(
color: String?,
mBuilder: NotificationCompat.Builder,
localIconColor: String?,
) {
val iconColor = when {
color != null && color != "" -> {
try {
Color.parseColor(color)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Couldn't parse color from Android options")
}
}
localIconColor != null && localIconColor != "" -> {
try {
Color.parseColor(localIconColor)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Couldn't parse color from android options")
}
}
else -> {
Log.d(TAG, "No icon color settings found")
0
}
}
if (iconColor != 0) {
mBuilder.color = iconColor
}
}
private fun getBitmapFromURL(strURL: String?): Bitmap? {
val input: InputStream? = null;
return try {
val url = URL(strURL)
val connection = (url.openConnection() as HttpURLConnection).apply {
connectTimeout = 15000
doInput = true
connect()
}
val input = connection.inputStream
input.close()
BitmapFactory.decodeStream(input)
} catch (e: IOException) {
null
}
finally {
if (input != null){
input?.close()
}
}
}
private fun parseNotificationIdToInt(extras: Bundle?): Int {
var returnVal = 0
try {
returnVal = extras!!.getString(PushConstants.NOT_ID)!!.toInt()
} catch (e: NumberFormatException) {
Log.e(TAG, "NumberFormatException occurred: ${PushConstants.NOT_ID}: ${e.message}")
} catch (e: Exception) {
Log.e(TAG, "Exception occurred when parsing ${PushConstants.NOT_ID}: ${e.message}")
}
return returnVal
}
private fun fromHtml(source: String?): Spanned? {
return if (source != null) Html.fromHtml(source) else null
}
private fun isAvailableSender(from: String?): Boolean {
val savedSenderID = pushSharedPref.getString(PushConstants.SENDER_ID, "")
Log.d(TAG, "sender id = $savedSenderID")
return from == savedSenderID || from!!.startsWith("/topics/")
}
}
package com.adobe.phonegap.push
/**
* Push Constants
*
* @todo add docs about each constant.
*/
@Suppress("HardCodedStringLiteral")
object PushConstants {
const val COM_ADOBE_PHONEGAP_PUSH: String = "com.adobe.phonegap.push"
const val REGISTRATION_ID: String = "registrationId"
const val REGISTRATION_TYPE: String = "registrationType"
const val FOREGROUND: String = "foreground"
const val TITLE: String = "title"
const val NOT_ID: String = "notId"
const val PUSH_BUNDLE: String = "pushBundle"
const val ICON: String = "icon"
const val ICON_COLOR: String = "iconColor"
const val SOUND: String = "sound"
const val SOUND_DEFAULT: String = "default"
const val SOUND_RINGTONE: String = "ringtone"
const val VIBRATE: String = "vibrate"
const val ACTIONS: String = "actions"
const val CALLBACK: String = "callback"
const val ACTION_CALLBACK: String = "actionCallback"
const val DRAWABLE: String = "drawable"
const val MSGCNT: String = "msgcnt"
const val VIBRATION_PATTERN: String = "vibrationPattern"
const val STYLE: String = "style"
const val SUMMARY_TEXT: String = "summaryText"
const val PICTURE: String = "picture"
const val GCM_N: String = "gcm.n."
const val GCM_NOTIFICATION: String = "gcm.notification"
const val GCM_NOTIFICATION_BODY: String = "gcm.notification.body"
const val UA_PREFIX: String = "com.urbanairship.push"
const val PARSE_COM_DATA: String = "data"
const val ALERT: String = "alert"
const val MESSAGE: String = "message"
const val BODY: String = "body"
const val SOUNDNAME: String = "soundname"
const val COLOR: String = "color"
const val LED_COLOR: String = "ledColor"
const val PRIORITY: String = "priority"
const val IMAGE: String = "image"
const val STYLE_INBOX: String = "inbox"
const val STYLE_PICTURE: String = "picture"
const val STYLE_TEXT: String = "text"
const val BADGE: String = "badge"
const val INITIALIZE: String = "init"
const val SUBSCRIBE: String = "subscribe"
const val UNSUBSCRIBE: String = "unsubscribe"
const val UNREGISTER: String = "unregister"
const val EXIT: String = "exit"
const val FINISH: String = "finish"
const val HAS_PERMISSION: String = "hasPermission"
const val ANDROID: String = "android"
const val SENDER_ID: String = "senderID"
const val CLEAR_BADGE: String = "clearBadge"
const val CLEAR_NOTIFICATIONS: String = "clearNotifications"
const val COLDSTART: String = "coldstart"
const val ADDITIONAL_DATA: String = "additionalData"
const val COUNT: String = "count"
const val FROM: String = "from"
const val COLLAPSE_KEY: String = "collapse_key"
const val FORCE_SHOW: String = "forceShow"
const val FCM: String = "FCM"
const val CONTENT_AVAILABLE: String = "content-available"
const val TOPICS: String = "topics"
const val SET_APPLICATION_ICON_BADGE_NUMBER: String = "setApplicationIconBadgeNumber"
const val GET_APPLICATION_ICON_BADGE_NUMBER: String = "getApplicationIconBadgeNumber"
const val CLEAR_ALL_NOTIFICATIONS: String = "clearAllNotifications"
const val VISIBILITY: String = "visibility"
const val INLINE_REPLY: String = "inlineReply"
const val INLINE_REPLY_LABEL: String = "replyLabel"
const val LOC_KEY: String = "locKey"
const val LOC_DATA: String = "locData"
const val TWILIO_BODY: String = "twi_body"
const val TWILIO_TITLE: String = "twi_title"
const val TWILIO_SOUND: String = "twi_sound"
const val AWS_PINPOINT_BODY: String = "pinpoint.notification.body"
const val AWS_PINPOINT_PICTURE: String = "pinpoint.notification.imageUrl"
const val AWS_PINPOINT_PREFIX: String = "pinpoint.notification"
const val MP_MESSAGE: String = "mp_message"
const val START_IN_BACKGROUND: String = "cdvStartInBackground"
const val FORCE_START: String = "force-start"
const val MESSAGE_KEY: String = "messageKey"
const val TITLE_KEY: String = "titleKey"
const val NO_CACHE: String = "no-cache"
const val DISMISSED: String = "dismissed"
const val IMAGE_TYPE: String = "image-type"
const val IMAGE_TYPE_SQUARE: String = "square"
const val IMAGE_TYPE_CIRCLE: String = "circle"
const val SUBJECT: String = "subject"
const val GOOGLE_APP_ID: String = "google_app_id"
const val GCM_DEFAULT_SENDER_ID: String = "gcm_defaultSenderId"
const val PUSH_DISMISSED: String = "push_dismissed"
const val DEFAULT_CHANNEL_ID: String = "PushPluginChannel"
const val CHANNELS: String = "channels"
const val CHANNEL_ID: String = "id"
const val CHANNEL_DESCRIPTION: String = "description"
const val CHANNEL_IMPORTANCE: String = "importance"
const val CHANNEL_LIGHT_COLOR: String = "lightColor"
const val CHANNEL_VIBRATION: String = "vibration"
const val ANDROID_CHANNEL_ID: String = "android_channel_id"
const val CHANNEL_STATE: String = "state"
const val CREATE_CHANNEL: String = "createChannel"
const val DELETE_CHANNEL: String = "deleteChannel"
const val ONGOING: String = "ongoing"
const val LIST_CHANNELS: String = "listChannels"
const val CLEAR_NOTIFICATION: String = "clearNotification"
const val MESSAGE_ID: String = "google.message_id"
const val IS_ENABLED: String = "isEnabled"
}
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
/**
*
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("LongLogTag")
class PushDismissedHandler : BroadcastReceiver() {
companion object {
private const val TAG: String = "${PushPlugin.PREFIX_TAG} (PushDismissedHandler)"
}
/**
* @param context
* @param intent
*/
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == PushConstants.PUSH_DISMISSED) {
val notID = intent.getIntExtra(PushConstants.NOT_ID, 0)
Log.d(TAG, "not id = $notID")
FCMService().setNotification(notID, "")
}
}
}
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.core.app.RemoteInput
/**
* Push Handler Activity
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("LongLogTag", "LogConditional")
class PushHandlerActivity : Activity() {
companion object {
private const val TAG: String = "${PushPlugin.PREFIX_TAG} (PushHandlerActivity)"
}
/**
* this activity will be started if the user touches a notification that we own.
* We send it's data off to the push plugin for processing.
* If needed, we boot up the main activity to kickstart the application.
*
* @param savedInstanceState
*
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
intent.extras?.let { extras ->
val notId = extras.getInt(PushConstants.NOT_ID, 0)
val callback = extras.getString(PushConstants.CALLBACK)
var foreground = extras.getBoolean(PushConstants.FOREGROUND, true)
val startOnBackground = extras.getBoolean(PushConstants.START_IN_BACKGROUND, false)
val dismissed = extras.getBoolean(PushConstants.DISMISSED, false)
FCMService().setNotification(notId, "")
if (!startOnBackground) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(FCMService.getAppName(this), notId)
}
val notHaveInlineReply = processPushBundle()
if (notHaveInlineReply && Build.VERSION.SDK_INT < Build.VERSION_CODES.N && !startOnBackground) {
foreground = true
}
Log.d(TAG, "Not ID: $notId")
Log.d(TAG, "Callback: $callback")
Log.d(TAG, "Foreground: $foreground")
Log.d(TAG, "Start On Background: $startOnBackground")
Log.d(TAG, "Dismissed: $dismissed")
finish()
if (!dismissed) {
Log.d(TAG, "Is Push Plugin Active: ${PushPlugin.isActive}")
if (!PushPlugin.isActive && foreground && notHaveInlineReply) {
Log.d(TAG, "Force Main Activity Reload: Start on Background = False")
forceMainActivityReload(false)
} else if (startOnBackground) {
Log.d(TAG, "Force Main Activity Reload: Start on Background = True")
forceMainActivityReload(true)
} else {
Log.d(TAG, "Don't Want Main Activity")
}
}
}
}
private fun processPushBundle(): Boolean {
/*
* Takes the pushBundle extras from the intent,
* and sends it through to the PushPlugin for processing.
*/
return intent.extras?.let { extras ->
var notHaveInlineReply = true
extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
putBoolean(PushConstants.FOREGROUND, false)
putBoolean(PushConstants.COLDSTART, !PushPlugin.isActive)
putBoolean(PushConstants.DISMISSED, extras.getBoolean(PushConstants.DISMISSED))
putString(
PushConstants.ACTION_CALLBACK,
extras.getString(PushConstants.CALLBACK)
)
remove(PushConstants.NO_CACHE)
RemoteInput.getResultsFromIntent(intent)?.let { results ->
val reply = results.getCharSequence(PushConstants.INLINE_REPLY).toString()
Log.d(TAG, "Inline Reply: $reply")
putString(PushConstants.INLINE_REPLY, reply)
notHaveInlineReply = false
}
PushPlugin.sendExtras(this)
}
return notHaveInlineReply
} ?: true
}
private fun forceMainActivityReload(startOnBackground: Boolean) {
/*
* Forces the main activity to re-launch if it's unloaded.
*/
val launchIntent = packageManager.getLaunchIntentForPackage(applicationContext.packageName)
intent.extras?.let { extras ->
launchIntent?.apply {
extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
putExtras(this)
}
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_FROM_BACKGROUND)
putExtra(PushConstants.START_IN_BACKGROUND, startOnBackground)
}
}
startActivity(launchIntent)
}
/**
* On Resuming of Activity
*/
override fun onResume() {
super.onResume()
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}
}
package com.adobe.phonegap.push
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources.NotFoundException
import android.media.AudioAttributes
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.gms.tasks.Tasks
import com.google.firebase.messaging.FirebaseMessaging
import me.leolin.shortcutbadger.ShortcutBadger
import org.apache.cordova.*
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.util.*
import java.util.concurrent.ExecutionException
/**
* Cordova Plugin Push
*/
@Suppress("HardCodedStringLiteral")
@SuppressLint("LongLogTag", "LogConditional")
class PushPlugin : CordovaPlugin() {
companion object {
const val PREFIX_TAG: String = "cordova-plugin-push"
private const val TAG: String = "$PREFIX_TAG (PushPlugin)"
/**
* Is the WebView in the foreground?
*/
var isInForeground: Boolean = false
private var pushContext: CallbackContext? = null
private var gWebView: CordovaWebView? = null
private val gCachedExtras = Collections.synchronizedList(ArrayList<Bundle>())
/**
*
*/
fun sendEvent(json: JSONObject?) {
val pluginResult = PluginResult(PluginResult.Status.OK, json)
.apply { keepCallback = true }
pushContext?.sendPluginResult(pluginResult)
}
/**
* Sends the push bundle extras to the client application. If the client
* application isn't currently active and the no-cache flag is not set, it is
* cached for later processing.
*
* @param extras
*/
@JvmStatic
fun sendExtras(extras: Bundle?) {
/**
* Serializes a bundle to JSON.
*
* @param extras
*
* @return JSONObject|null
*/
fun convertBundleToJson(extras: Bundle): JSONObject? {
Log.d(TAG, "Convert Extras to JSON")
try {
val json = JSONObject()
val additionalData = JSONObject()
// Add any keys that need to be in top level json to this set
val jsonKeySet: HashSet<String?> = HashSet<String?>()
Collections.addAll(
jsonKeySet,
PushConstants.TITLE,
PushConstants.MESSAGE,
PushConstants.COUNT,
PushConstants.SOUND,
PushConstants.IMAGE
)
val it: Iterator<String> = extras.keySet().iterator()
while (it.hasNext()) {
val key = it.next()
val value = extras[key]
Log.d(TAG, "Extras Iteration: key=$key")
when {
jsonKeySet.contains(key) -> {
json.put(key, value)
}
key == PushConstants.COLDSTART -> {
additionalData.put(key, extras.getBoolean(PushConstants.COLDSTART))
}
key == PushConstants.FOREGROUND -> {
additionalData.put(key, extras.getBoolean(PushConstants.FOREGROUND))
}
key == PushConstants.DISMISSED -> {
additionalData.put(key, extras.getBoolean(PushConstants.DISMISSED))
}
value is String -> {
try {
// Try to figure out if the value is another JSON object
when {
value.startsWith("{") -> {
additionalData.put(key, JSONObject(value))
}
value.startsWith("[") -> {
additionalData.put(key, JSONArray(value))
}
else -> {
additionalData.put(key, value)
}
}
} catch (e: Exception) {
additionalData.put(key, value)
}
}
}
}
json.put(PushConstants.ADDITIONAL_DATA, additionalData)
Log.v(TAG, "Extras To JSON Result: $json")
return json
} catch (e: JSONException) {
Log.e(TAG, "convertBundleToJson had a JSON Exception")
}
return null
}
extras?.let {
val noCache = it.getString(PushConstants.NO_CACHE)
if (gWebView != null) {
sendEvent(convertBundleToJson(extras))
} else if (noCache != "1") {
Log.v(TAG, "sendExtras: Caching extras to send at a later time.")
gCachedExtras.add(extras)
}
}
}
/**
* Retrieves the badge count from SharedPreferences
*
* @param context
*
* @return Int
*/
fun getApplicationIconBadgeNumber(context: Context): Int {
val settings = context.getSharedPreferences(PushConstants.BADGE, Context.MODE_PRIVATE)
return settings.getInt(PushConstants.BADGE, 0)
}
/**
* Sets badge count on application icon and in SharedPreferences
*
* @param context
* @param badgeCount
*/
@JvmStatic
fun setApplicationIconBadgeNumber(context: Context, badgeCount: Int) {
if (badgeCount > 0) {
ShortcutBadger.applyCount(context, badgeCount)
} else {
ShortcutBadger.removeCount(context)
}
context.getSharedPreferences(PushConstants.BADGE, Context.MODE_PRIVATE)
.edit()?.apply {
putInt(PushConstants.BADGE, badgeCount.coerceAtLeast(0))
apply()
}
}
/**
* @return Boolean Active is true when the Cordova WebView is present.
*/
val isActive: Boolean
get() = gWebView != null
}
private val activity: Activity
get() = cordova.activity
private val applicationContext: Context
get() = activity.applicationContext
private val notificationManager: NotificationManager
get() = (activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
private val appName: String
get() = activity.packageManager.getApplicationLabel(activity.applicationInfo) as String
@TargetApi(26)
@Throws(JSONException::class)
private fun listChannels(): JSONArray {
val channels = JSONArray()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannels = notificationManager.notificationChannels
for (notificationChannel in notificationChannels) {
val channel = JSONObject().apply {
put(PushConstants.CHANNEL_ID, notificationChannel.id)
put(PushConstants.CHANNEL_DESCRIPTION, notificationChannel.description)
}
channels.put(channel)
}
}
return channels
}
@TargetApi(26)
private fun deleteChannel(channelId: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.deleteNotificationChannel(channelId)
}
}
@TargetApi(26)
@Throws(JSONException::class)
private fun createChannel(channel: JSONObject?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
channel?.let {
NotificationChannel(
it.getString(PushConstants.CHANNEL_ID),
it.optString(PushConstants.CHANNEL_DESCRIPTION, appName),
it.optInt(PushConstants.CHANNEL_IMPORTANCE, NotificationManager.IMPORTANCE_DEFAULT)
).apply {
/**
* Enable Lights when Light Color is set.
*/
val mLightColor = it.optInt(PushConstants.CHANNEL_LIGHT_COLOR, -1)
if (mLightColor != -1) {
enableLights(true)
lightColor = mLightColor
}
/**
* Set Lock Screen Visibility.
*/
lockscreenVisibility = channel.optInt(
PushConstants.VISIBILITY,
NotificationCompat.VISIBILITY_PUBLIC
)
/**
* Set if badge should be shown
*/
setShowBadge(it.optBoolean(PushConstants.BADGE, true))
/**
* Sound Settings
*/
val (soundUri, audioAttributes) = getNotificationChannelSound(it)
setSound(soundUri, audioAttributes)
/**
* Set vibration settings.
* Data can be either JSONArray or Boolean value.
*/
val (hasVibration, vibrationPatternArray) = getNotificationChannelVibration(it)
if (vibrationPatternArray != null) {
vibrationPattern = vibrationPatternArray
} else {
enableVibration(hasVibration)
}
notificationManager.createNotificationChannel(this)
}
}
}
}
private fun getNotificationChannelSound(channelData: JSONObject): Pair<Uri?, AudioAttributes?> {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build()
val sound = channelData.optString(PushConstants.SOUND, PushConstants.SOUND_DEFAULT)
return when {
sound == PushConstants.SOUND_RINGTONE -> Pair(
Settings.System.DEFAULT_RINGTONE_URI,
audioAttributes
)
// Disable sound for this notification channel if an empty string is passed.
// https://stackoverflow.com/a/47144981/6194193
sound.isEmpty() -> Pair(null, null)
// E.g. android.resource://org.apache.cordova/raw/<SOUND>
sound != PushConstants.SOUND_DEFAULT -> {
val scheme = ContentResolver.SCHEME_ANDROID_RESOURCE
val packageName = applicationContext.packageName
Pair(
Uri.parse("${scheme}://$packageName/raw/$sound"),
audioAttributes
)
}
else -> Pair(Settings.System.DEFAULT_NOTIFICATION_URI, audioAttributes)
}
}
private fun getNotificationChannelVibration(channelData: JSONObject): Pair<Boolean, LongArray?> {
var patternArray: LongArray? = null
val mVibrationPattern = channelData.optJSONArray(PushConstants.CHANNEL_VIBRATION)
if (mVibrationPattern != null) {
val patternLength = mVibrationPattern.length()
patternArray = LongArray(patternLength)
for (i in 0 until patternLength) {
patternArray[i] = mVibrationPattern.optLong(i)
}
}
return Pair(
channelData.optBoolean(PushConstants.CHANNEL_VIBRATION, true),
patternArray
)
}
@TargetApi(26)
private fun createDefaultNotificationChannelIfNeeded(options: JSONObject?) {
// only call on Android O and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channels = notificationManager.notificationChannels
for (i in channels.indices) {
if (PushConstants.DEFAULT_CHANNEL_ID == channels[i].id) {
return
}
}
try {
options?.apply {
put(PushConstants.CHANNEL_ID, PushConstants.DEFAULT_CHANNEL_ID)
putOpt(PushConstants.CHANNEL_DESCRIPTION, appName)
}
createChannel(options)
} catch (e: JSONException) {
Log.e(TAG, "Execute: JSON Exception ${e.message}")
}
}
}
/**
* Performs various push plugin related tasks:
*
* - Initialize
* - Unregister
* - Has Notification Permission Check
* - Set Icon Badge Number
* - Get Icon Badge Number
* - Clear All Notifications
* - Clear Notification
* - Subscribe
* - Unsubscribe
* - Create Channel
* - Delete Channel
* - List Channels
*
* @param action
* @param data
* @param callbackContext
*/
override fun execute(
action: String,
data: JSONArray,
callbackContext: CallbackContext
): Boolean {
Log.v(TAG, "Execute: Action = $action")
gWebView = webView
when (action) {
PushConstants.INITIALIZE -> executeActionInitialize(data, callbackContext)
PushConstants.UNREGISTER -> executeActionUnregister(data, callbackContext)
PushConstants.FINISH -> callbackContext.success()
PushConstants.HAS_PERMISSION -> executeActionHasPermission(callbackContext)
PushConstants.SET_APPLICATION_ICON_BADGE_NUMBER -> executeActionSetIconBadgeNumber(
data, callbackContext
)
PushConstants.GET_APPLICATION_ICON_BADGE_NUMBER -> executeActionGetIconBadgeNumber(
callbackContext
)
PushConstants.CLEAR_ALL_NOTIFICATIONS -> executeActionClearAllNotifications(callbackContext)
PushConstants.SUBSCRIBE -> executeActionSubscribe(data, callbackContext)
PushConstants.UNSUBSCRIBE -> executeActionUnsubscribe(data, callbackContext)
PushConstants.CREATE_CHANNEL -> executeActionCreateChannel(data, callbackContext)
PushConstants.DELETE_CHANNEL -> executeActionDeleteChannel(data, callbackContext)
PushConstants.LIST_CHANNELS -> executeActionListChannels(callbackContext)
PushConstants.CLEAR_NOTIFICATION -> executeActionClearNotification(data, callbackContext)
else -> {
Log.e(TAG, "Execute: Invalid Action $action")
callbackContext.sendPluginResult(PluginResult(PluginResult.Status.INVALID_ACTION))
return false
}
}
return true
}
private fun executeActionInitialize(data: JSONArray, callbackContext: CallbackContext) {
// Better Logging
fun formatLogMessage(msg: String): String = "Execute::Initialize: ($msg)"
cordova.threadPool.execute(Runnable {
Log.v(TAG, formatLogMessage("Data=$data"))
pushContext = callbackContext
val sharedPref = applicationContext.getSharedPreferences(
PushConstants.COM_ADOBE_PHONEGAP_PUSH,
Context.MODE_PRIVATE
)
var jo: JSONObject? = null
var senderID: String? = null
try {
jo = data.getJSONObject(0).getJSONObject(PushConstants.ANDROID)
val senderIdResId = activity.resources.getIdentifier(
PushConstants.GCM_DEFAULT_SENDER_ID,
"string",
activity.packageName
)
senderID = activity.getString(senderIdResId)
// If no NotificationChannels exist create the default one
createDefaultNotificationChannelIfNeeded(jo)
Log.v(TAG, formatLogMessage("JSONObject=$jo"))
Log.v(TAG, formatLogMessage("senderID=$senderID"))
val token = try {
try {
Tasks.await(FirebaseMessaging.getInstance().token)
} catch (e: ExecutionException) {
throw e.cause ?: e
}
} catch (e: IllegalStateException) {
Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
null
} catch (e: ExecutionException) {
Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
null
} catch (e: InterruptedException) {
Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
null
}
if (token != "") {
val registration = JSONObject().put(PushConstants.REGISTRATION_ID, token).apply {
put(PushConstants.REGISTRATION_TYPE, PushConstants.FCM)
}
Log.v(TAG, formatLogMessage("onRegistered=$registration"))
val topics = jo.optJSONArray(PushConstants.TOPICS)
subscribeToTopics(topics)
sendEvent(registration)
} else {
callbackContext.error("Empty registration ID received from FCM")
return@Runnable
}
} catch (e: JSONException) {
Log.e(TAG, formatLogMessage("JSON Exception ${e.message}"))
callbackContext.error(e.message)
} catch (e: IOException) {
Log.e(TAG, formatLogMessage("IO Exception ${e.message}"))
callbackContext.error(e.message)
} catch (e: NotFoundException) {
Log.e(TAG, formatLogMessage("Resources NotFoundException Exception ${e.message}"))
callbackContext.error(e.message)
}
jo?.let {
/**
* Add Shared Preferences
*
* Make sure to remove the preferences in the Remove step.
*/
sharedPref.edit()?.apply {
/**
* Set Icon
*/
try {
putString(PushConstants.ICON, it.getString(PushConstants.ICON))
} catch (e: JSONException) {
Log.d(TAG, formatLogMessage("No Icon Options"))
}
/**
* Set Icon Color
*/
try {
putString(PushConstants.ICON_COLOR, it.getString(PushConstants.ICON_COLOR))
} catch (e: JSONException) {
Log.d(TAG, formatLogMessage("No Icon Color Options"))
}
/**
* Clear badge count when true
*/
val clearBadge = it.optBoolean(PushConstants.CLEAR_BADGE, false)
putBoolean(PushConstants.CLEAR_BADGE, clearBadge)
if (clearBadge) {
setApplicationIconBadgeNumber(applicationContext, 0)
}
/**
* Set Sound
*/
putBoolean(PushConstants.SOUND, it.optBoolean(PushConstants.SOUND, true))
/**
* Set Vibrate
*/
putBoolean(PushConstants.VIBRATE, it.optBoolean(PushConstants.VIBRATE, true))
/**
* Set Clear Notifications
*/
putBoolean(
PushConstants.CLEAR_NOTIFICATIONS,
it.optBoolean(PushConstants.CLEAR_NOTIFICATIONS, true)
)
/**
* Set Force Show
*/
putBoolean(
PushConstants.FORCE_SHOW,
it.optBoolean(PushConstants.FORCE_SHOW, false)
)
/**
* Set SenderID
*/
putString(PushConstants.SENDER_ID, senderID)
/**
* Set Message Key
*/
putString(PushConstants.MESSAGE_KEY, it.optString(PushConstants.MESSAGE_KEY))
/**
* Set Title Key
*/
putString(PushConstants.TITLE_KEY, it.optString(PushConstants.TITLE_KEY))
commit()
}
}
if (gCachedExtras.isNotEmpty()) {
Log.v(TAG, formatLogMessage("Sending Cached Extras"))
synchronized(gCachedExtras) {
val gCachedExtrasIterator: Iterator<Bundle> = gCachedExtras.iterator()
while (gCachedExtrasIterator.hasNext()) {
sendExtras(gCachedExtrasIterator.next())
}
}
gCachedExtras.clear()
}
})
}
private fun executeActionUnregister(data: JSONArray, callbackContext: CallbackContext) {
// Better Logging
fun formatLogMessage(msg: String): String = "Execute::Unregister: ($msg)"
cordova.threadPool.execute {
try {
val sharedPref = applicationContext.getSharedPreferences(
PushConstants.COM_ADOBE_PHONEGAP_PUSH,
Context.MODE_PRIVATE
)
val topics = data.optJSONArray(0)
if (topics != null) {
unsubscribeFromTopics(topics)
} else {
try {
Tasks.await(FirebaseMessaging.getInstance().deleteToken())
} catch (e: ExecutionException) {
throw e.cause ?: e
}
Log.v(TAG, formatLogMessage("UNREGISTER"))
/**
* Remove Shared Preferences
*
* Make sure to remove what was in the Initialize step.
*/
sharedPref.edit()?.apply {
remove(PushConstants.ICON)
remove(PushConstants.ICON_COLOR)
remove(PushConstants.CLEAR_BADGE)
remove(PushConstants.SOUND)
remove(PushConstants.VIBRATE)
remove(PushConstants.CLEAR_NOTIFICATIONS)
remove(PushConstants.FORCE_SHOW)
remove(PushConstants.SENDER_ID)
remove(PushConstants.MESSAGE_KEY)
remove(PushConstants.TITLE_KEY)
commit()
}
}
callbackContext.success()
} catch (e: IOException) {
Log.e(TAG, formatLogMessage("IO Exception ${e.message}"))
callbackContext.error(e.message)
} catch (e: InterruptedException) {
Log.e(TAG, formatLogMessage("Interrupted ${e.message}"))
callbackContext.error(e.message)
}
}
}
private fun executeActionHasPermission(callbackContext: CallbackContext) {
// Better Logging
fun formatLogMessage(msg: String): String = "Execute::HasPermission: ($msg)"
cordova.threadPool.execute {
try {
val isNotificationEnabled = NotificationManagerCompat.from(applicationContext)
.areNotificationsEnabled()
Log.d(TAG, formatLogMessage("Has Notification Permission: $isNotificationEnabled"))
val jo = JSONObject().apply {
put(PushConstants.IS_ENABLED, isNotificationEnabled)
}
val pluginResult = PluginResult(PluginResult.Status.OK, jo).apply {
keepCallback = true
}
callbackContext.sendPluginResult(pluginResult)
} catch (e: UnknownError) {
callbackContext.error(e.message)
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionSetIconBadgeNumber(data: JSONArray, callbackContext: CallbackContext) {
fun formatLogMessage(msg: String): String = "Execute::SetIconBadgeNumber: ($msg)"
cordova.threadPool.execute {
Log.v(TAG, formatLogMessage("data=$data"))
try {
val badgeCount = data.getJSONObject(0).getInt(PushConstants.BADGE)
setApplicationIconBadgeNumber(applicationContext, badgeCount)
} catch (e: JSONException) {
callbackContext.error(e.message)
}
callbackContext.success()
}
}
private fun executeActionGetIconBadgeNumber(callbackContext: CallbackContext) {
cordova.threadPool.execute {
Log.v(TAG, "Execute::GetIconBadgeNumber")
callbackContext.success(getApplicationIconBadgeNumber(applicationContext))
}
}
private fun executeActionClearAllNotifications(callbackContext: CallbackContext) {
cordova.threadPool.execute {
Log.v(TAG, "Execute Clear All Notifications")
clearAllNotifications()
callbackContext.success()
}
}
private fun executeActionSubscribe(data: JSONArray, callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
Log.v(TAG, "Execute::Subscribe")
val topic = data.getString(0)
subscribeToTopic(topic)
callbackContext.success()
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionUnsubscribe(data: JSONArray, callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
Log.v(TAG, "Execute::Unsubscribe")
val topic = data.getString(0)
unsubscribeFromTopic(topic)
callbackContext.success()
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionCreateChannel(data: JSONArray, callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
Log.v(TAG, "Execute::CreateChannel")
createChannel(data.getJSONObject(0))
callbackContext.success()
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionDeleteChannel(data: JSONArray, callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
val channelId = data.getString(0)
Log.v(TAG, "Execute::DeleteChannel channelId=$channelId")
deleteChannel(channelId)
callbackContext.success()
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionListChannels(callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
Log.v(TAG, "Execute::ListChannels")
callbackContext.success(listChannels())
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
private fun executeActionClearNotification(data: JSONArray, callbackContext: CallbackContext) {
cordova.threadPool.execute {
try {
val notificationId = data.getInt(0)
Log.v(TAG, "Execute::ClearNotification notificationId=$notificationId")
clearNotification(notificationId)
callbackContext.success()
} catch (e: JSONException) {
callbackContext.error(e.message)
}
}
}
/**
* Initialize
*/
override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) {
super.initialize(cordova, webView)
isInForeground = true
}
/**
* Handle when the view is being paused
*/
override fun onPause(multitasking: Boolean) {
isInForeground = false
super.onPause(multitasking)
}
/**
* Handle when the view is resuming
*/
override fun onResume(multitasking: Boolean) {
super.onResume(multitasking)
isInForeground = true
}
/**
* Handle when the view is being destroyed
*/
override fun onDestroy() {
isInForeground = false
gWebView = null
// Clear Notification
applicationContext.getSharedPreferences(
PushConstants.COM_ADOBE_PHONEGAP_PUSH,
Context.MODE_PRIVATE
).apply {
if (getBoolean(PushConstants.CLEAR_NOTIFICATIONS, true)) {
clearAllNotifications()
}
}
super.onDestroy()
}
private fun clearAllNotifications() {
notificationManager.cancelAll()
}
private fun clearNotification(id: Int) {
notificationManager.cancel(appName, id)
}
private fun subscribeToTopics(topics: JSONArray?) {
topics?.let {
for (i in 0 until it.length()) {
val topicKey = it.optString(i, null)
subscribeToTopic(topicKey)
}
}
}
private fun unsubscribeFromTopics(topics: JSONArray?) {
topics?.let {
for (i in 0 until it.length()) {
val topic = it.optString(i, null)
unsubscribeFromTopic(topic)
}
}
}
private fun subscribeToTopic(topic: String?) {
topic?.let {
Log.d(TAG, "Subscribing to Topic: $it")
FirebaseMessaging.getInstance().subscribeToTopic(it)
}
}
private fun unsubscribeFromTopic(topic: String?) {
topic?.let {
Log.d(TAG, "Unsubscribing to topic: $it")
FirebaseMessaging.getInstance().unsubscribeFromTopic(it)
}
}
}
var messageChannel;
self.addEventListener('install', function (event) {
self.skipWaiting();
});
self.addEventListener('push', function (event) {
// parse incoming message
var obj = {};
var pushData = {
image: 'https://avatars1.githubusercontent.com/u/60365?v=3&s=200',
additionalData: {}
};
if (event.data) {
obj = event.data.json();
}
console.log(obj);
// convert to push plugin API
for (var key in obj) {
if (key === 'title') {
pushData.title = obj[key];
} else if (key === 'message' || key === 'body') {
pushData.message = obj[key];
} else if (key === 'count' || key === 'msgcnt' || key === 'badge') {
pushData.count = obj[key];
} else if (key === 'sound' || key === 'soundname') {
pushData.sound = obj[key];
} else if (key === 'image') {
pushData.image = obj[key];
} else {
pushData.additionalData[key] = obj[key];
}
}
event.waitUntil(
self.registration.showNotification(pushData.title, {
body: pushData.message,
icon: pushData.image,
tag: 'simple-push-demo-notification-tag'
})
);
messageChannel.ports[0].postMessage(pushData);
});
self.addEventListener('message', function (event) {
messageChannel = event;
});
{
"name": "Push Demo",
"gcm_sender_id": "996231231186"
}
//
// AppDelegate+notification.h
// pushtest
//
// Created by Robert Easterday on 10/26/12.
//
//
#import "AppDelegate.h"
@import UserNotifications;
extern NSString *const pushPluginApplicationDidBecomeActiveNotification;
@interface AppDelegate (notification) <UNUserNotificationCenterDelegate>
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:( void (^)(UIBackgroundFetchResult))completionHandler;
- (void)pushPluginOnApplicationDidBecomeActive:(UIApplication *)application;
- (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler;
- (id) getCommandInstance:(NSString*)className;
@property (nonatomic, retain) NSDictionary *launchNotification;
@property (nonatomic, retain) NSNumber *coldstart;
@end
//
// AppDelegate+notification.m
// pushtest
//
// Created by Robert Easterday on 10/26/12.
//
//
#import "AppDelegate+notification.h"
#import "PushPlugin.h"
#import <objc/runtime.h>
static char launchNotificationKey;
static char coldstartKey;
NSString *const pushPluginApplicationDidBecomeActiveNotification = @"pushPluginApplicationDidBecomeActiveNotification";
@implementation AppDelegate (notification)
- (id) getCommandInstance:(NSString*)className
{
return [self.viewController getCommandInstance:className];
}
// its dangerous to override a method from within a category.
// Instead we will use method swizzling. we set this up in the load call.
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(init);
SEL swizzledSelector = @selector(pushPluginSwizzledInit);
Method original = class_getInstanceMethod(class, originalSelector);
Method swizzled = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzled),
method_getTypeEncoding(swizzled));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(original),
method_getTypeEncoding(original));
} else {
method_exchangeImplementations(original, swizzled);
}
});
}
- (AppDelegate *)pushPluginSwizzledInit
{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(pushPluginOnApplicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
// This actually calls the original init method over in AppDelegate. Equivilent to calling super
// on an overrided method, this is not recursive, although it appears that way. neat huh?
return [self pushPluginSwizzledInit];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
[pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
[pushHandler didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"didReceiveNotification with fetchCompletionHandler");
// app is in the background or inactive, so only call notification callback if this is a silent push
if (application.applicationState != UIApplicationStateActive) {
NSLog(@"app in-active");
// do some convoluted logic to find out if this should be a silent push.
long silent = 0;
id aps = [userInfo objectForKey:@"aps"];
id contentAvailable = [aps objectForKey:@"content-available"];
if ([contentAvailable isKindOfClass:[NSString class]] && [contentAvailable isEqualToString:@"1"]) {
silent = 1;
} else if ([contentAvailable isKindOfClass:[NSNumber class]]) {
silent = [contentAvailable integerValue];
}
if (silent == 1) {
NSLog(@"this should be a silent push");
void (^safeHandler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result){
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(result);
});
};
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
if (pushHandler.handlerObj == nil) {
pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
}
id notId = [userInfo objectForKey:@"notId"];
if (notId != nil) {
NSLog(@"Push Plugin notId %@", notId);
[pushHandler.handlerObj setObject:safeHandler forKey:notId];
} else {
NSLog(@"Push Plugin notId handler");
[pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
}
pushHandler.notificationMessage = userInfo;
pushHandler.isInline = NO;
[pushHandler notificationReceived];
} else {
NSLog(@"just put it in the shade");
//save it for later
self.launchNotification = userInfo;
completionHandler(UIBackgroundFetchResultNewData);
}
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}
- (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler
{
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
switch (settings.authorizationStatus)
{
case UNAuthorizationStatusDenied:
case UNAuthorizationStatusNotDetermined:
completionHandler(NO);
break;
case UNAuthorizationStatusAuthorized:
completionHandler(YES);
break;
}
}];
}
- (void)pushPluginOnApplicationDidBecomeActive:(NSNotification *)notification {
NSLog(@"active");
NSString *firstLaunchKey = @"firstLaunchKey";
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"phonegap-plugin-push"];
if (![defaults boolForKey:firstLaunchKey]) {
NSLog(@"application first launch: remove badge icon number");
[defaults setBool:YES forKey:firstLaunchKey];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
UIApplication *application = notification.object;
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
if (pushHandler.clearBadge) {
NSLog(@"PushPlugin clearing badge");
//zero badge
application.applicationIconBadgeNumber = 0;
} else {
NSLog(@"PushPlugin skip clear badge");
}
if (self.launchNotification) {
pushHandler.isInline = NO;
pushHandler.coldstart = [self.coldstart boolValue];
pushHandler.notificationMessage = self.launchNotification;
self.launchNotification = nil;
self.coldstart = [NSNumber numberWithBool:NO];
[pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
}
[[NSNotificationCenter defaultCenter] postNotificationName:pushPluginApplicationDidBecomeActiveNotification object:nil];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
NSLog( @"NotificationCenter Handle push from foreground" );
// custom code to handle push while app is in the foreground
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
pushHandler.notificationMessage = notification.request.content.userInfo;
pushHandler.isInline = YES;
// [pushHandler notificationReceived];
// completionHandler(UNNotificationPresentationOptionNone);
completionHandler(UNNotificationPresentationOptionAlert);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)(void))completionHandler
{
NSLog(@"Push Plugin didReceiveNotificationResponse: actionIdentifier %@, notification: %@", response.actionIdentifier,
response.notification.request.content.userInfo);
NSMutableDictionary *userInfo = [response.notification.request.content.userInfo mutableCopy];
[userInfo setObject:response.actionIdentifier forKey:@"actionCallback"];
NSLog(@"Push Plugin userInfo %@", userInfo);
switch ([UIApplication sharedApplication].applicationState) {
case UIApplicationStateActive:
{
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
pushHandler.notificationMessage = userInfo;
pushHandler.isInline = YES;
[pushHandler notificationReceived];
completionHandler();
break;
}
case UIApplicationStateInactive:
{
NSLog(@"coldstart");
self.launchNotification = response.notification.request.content.userInfo;
self.coldstart = [NSNumber numberWithBool:YES];
break;
}
case UIApplicationStateBackground:
{
void (^safeHandler)(void) = ^(void){
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();
});
};
PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
if (pushHandler.handlerObj == nil) {
pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
}
id notId = [userInfo objectForKey:@"notId"];
if (notId != nil) {
NSLog(@"Push Plugin notId %@", notId);
[pushHandler.handlerObj setObject:safeHandler forKey:notId];
} else {
NSLog(@"Push Plugin notId handler");
[pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
}
pushHandler.notificationMessage = userInfo;
pushHandler.isInline = NO;
[pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
}
}
}
// The accessors use an Associative Reference since you can't define a iVar in a category
// http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html
- (NSMutableArray *)launchNotification
{
return objc_getAssociatedObject(self, &launchNotificationKey);
}
- (void)setLaunchNotification:(NSDictionary *)aDictionary
{
objc_setAssociatedObject(self, &launchNotificationKey, aDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)coldstart
{
return objc_getAssociatedObject(self, &coldstartKey);
}
- (void)setColdstart:(NSNumber *)aNumber
{
objc_setAssociatedObject(self, &coldstartKey, aNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)dealloc
{
self.launchNotification = nil; // clear the association and release the object
self.coldstart = nil;
}
@end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment