├── .gitignore
├── LICENSE
├── README.md
└── src
├── misc
├── AndroidManifest.tmpl.xml
└── build.tmpl.gradle
└── python
├── buildozer.spec
├── libs
├── ContextHolder.java
├── KivyFirebaseMessagingBackgroundExecutor.java
├── KivyFirebaseMessagingBackgroundService.java
├── KivyFirebaseMessagingReceiver.java
├── KivyFirebaseMessagingService.java
├── KivyFirebaseMessagingStore.java
├── KivyFirebaseMessagingUtils.java
└── PlatformIntermediate.java
├── main.py
├── pushyy
├── __init__.py
├── pushyy.py
└── remote_message.py
└── python_notification_handler.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | # *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | .buildozer/
132 | bin/
133 | libs/
134 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Thomas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pushyy
2 | A Python module meant to simplify working with push notifications in Kivy
3 |
4 | **Video tutorial**: https://youtu.be/8nrXsWeRG8I
5 |
6 | Features
7 | --------------
8 | - Receive push notifications when your app is in the **foreground**
9 | - Receive push notifications when your app is in the **background** or not running
10 | - Run a background function when notification is received when app is not running
11 | - Get a device token
12 | - Listen for device token changes
13 |
14 | Credits
15 | --------------
16 | - [Flutter Firebase Messaging](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging/firebase_messaging): The Java classes are from there
17 |
18 | - Kindly create a PR correcting the copyright if it's wrong :)
19 | - [Electrum](https://github.com/spesmilo/electrum/tree/master/electrum) for the [onNewIntent](https://github.com/spesmilo/electrum/blob/6650e6bbae12a79e12667857ee039f1b1f30c7e3/electrum/gui/kivy/main_window.py#L620)
20 |
21 | Usage Overview
22 | --------------
23 | ```python
24 | from pushyy import Pushyy
25 |
26 | # Get device token
27 | def my_token_callback(token: str) -> None:
28 | send_to_server(token)
29 |
30 | Pushyy().get_device_token(my_token_callback)
31 |
32 | # Listen for new device token
33 | def new_token_callback(token: str) -> None:
34 | print(token)
35 |
36 | Pushyy().token_change_listener(new_token_callback)
37 |
38 | # Get notification data when app is in foreground
39 | def my_foreground_message_callback(notification_data: RemoteMessage) -> None:
40 | print(notification_data)
41 |
42 | Pushyy().foreground_message_handler(my_foreground_message_callback)
43 |
44 | # Get notification data when user taps on notification from tray
45 | def my_notification_click_callback(notification_data: RemoteMessage) -> None:
46 | print(notification_data)
47 |
48 | Pushyy().notification_click_handler(my_notification_click_callback)
49 |
50 | ```
51 | > See `src/python/main.py` on how the UI is being updated
52 |
53 | ##### Background function
54 | To run custom code in the background when a notification is received and your application is not running, write your code in the ```my_background_callback``` function in [python_notification_handler.py](src/python/python_notification_handler.py)
55 | ```python
56 | def my_background_callback(notification_data: RemoteMessage) -> None:
57 | """
58 | Note: Application is not visible to the user here
59 | One of the things you can do here: Mark a chat message
60 | as delivered by making a request to your server.
61 | """
62 | try:
63 | # connect to server
64 | pass
65 | except:
66 | pass
67 | ```
68 |
69 | Set up
70 | --------------
71 | ##### Part 1
72 | 1. Clone [python-for-android](https://github.com/kivy/python-for-android)
73 | 2. Open the file `pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle`
74 | 3. Add the following:
75 | - Under `buildscript->dependencies` add `classpath 'com.google.gms:google-services:4.3.4'`
76 | - Below `apply plugin: 'com.android.application'` add `apply plugin: 'com.google.gms.google-services'`
77 | - Under `dependencies` add `implementation platform('com.google.firebase:firebase-bom:X.Y.Z')` (replace XYZ with the latest version from [here](https://firebase.google.com/docs/android/learn-more#bom), tested with `28.1.0`)
78 | 4. Open the file `pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml`
79 | 5. Before the `` tag, add
80 | ```xml
81 |
85 |
87 |
88 |
89 |
90 |
91 |
95 |
96 |
97 |
98 |
99 | ```
100 | 6. Create a Firebase project [here](https://console.firebase.google.com/)
101 | - Add an Android app and skip the steps since we already did that at
102 | - Download the `google-services.json`
103 | - Move it to `pythonforandroid/bo -> Noneotstraps/common/build/` folder
104 | ##### Part 2
105 | 1. Place [pushyy.py](src/python/pushyy.py) and [python_notification_handler.py](src/python/python_notification_handler.py) next to your `main.py`
106 | 2. Place [libs/](src/python/libs) in the same folder as `buildozer.spec`
107 | 3. In your `buildozer.spec` find and set:
108 | ```bash
109 | android.add_src = libs/
110 | android.gradle_dependencies = com.google.firebase:firebase-messaging,com.google.firebase:firebase-analytics,com.google.code.gson:gson:2.8.6
111 | p4a.source_dir = /path/to/cloned/python-for-android
112 |
113 | services = PythonNotificationHandler:python_notification_handler.py
114 | # NB: File name must be python_notification_handler.py
115 | ```
116 | 5. Open `PlatformIntermediate.java` from your `libs/` folder and replace `com.waterfall.youtube` with `your.app.packagename`
117 |
118 | 6. Open `KivyFirebaseMessagingBackgroundExecutor.java` from your `libs/` folder and replace `com.waterfall.youtube` with `your.app.packagename`
119 |
120 | Notes
121 | ---------
122 | - This module is aimed for Android. For iOS, you may consider [this](https://youtu.be/mONyhxt2KV8) video
123 | - Just to clarify, with [Plyer](https://github.com/kivy/plyer) you get to show local notifications, not push notifications. [Which notifications should i use, Push notification or local notification?](https://stackoverflow.com/questions/45343427/which-notifications-should-i-use-push-notification-or-local-notification)
124 | - Why Pushyy? First thing that came to mind `¯\_(ツ)_/¯`
125 |
--------------------------------------------------------------------------------
/src/misc/AndroidManifest.tmpl.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 | = 9 %}
17 | android:xlargeScreens="true"
18 | {% endif %}
19 | />
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {% for perm in args.permissions %}
30 | {% if '.' in perm %}
31 |
32 | {% else %}
33 |
34 | {% endif %}
35 | {% endfor %}
36 |
37 | {% if args.wakelock %}
38 |
39 | {% endif %}
40 |
41 | {% if args.billing_pubkey %}
42 |
43 | {% endif %}
44 |
45 | {{ args.extra_manifest_xml }}
46 |
47 |
48 |
57 |
67 | {% for l in args.android_used_libs %}
68 |
69 | {% endfor %}
70 |
71 | {% for m in args.meta_data %}
72 | {% endfor %}
73 |
74 |
75 |
83 |
84 | {% if args.launcher %}
85 |
86 |
87 |
88 |
89 |
90 | {% else %}
91 |
92 |
93 |
94 |
95 | {% endif %}
96 |
97 | {%- if args.intent_filters -%}
98 | {{- args.intent_filters -}}
99 | {%- endif -%}
100 |
101 |
102 | {% if args.launcher %}
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {% endif %}
114 |
115 | {% if service or args.launcher %}
116 |
118 | {% endif %}
119 | {% for name in service_names %}
120 |
122 | {% endfor %}
123 | {% for name in native_services %}
124 |
125 | {% endfor %}
126 |
127 | {% if args.billing_pubkey %}
128 |
130 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | {% endif %}
139 |
143 |
145 |
146 |
147 |
148 |
149 |
153 |
154 |
155 |
156 |
157 | {% for a in args.add_activity %}
158 |
159 | {% endfor %}
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/src/misc/build.tmpl.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.5.2'
9 | classpath 'com.google.gms:google-services:4.3.4'
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | jcenter()
17 | {%- for repo in args.gradle_repositories %}
18 | {{repo}}
19 | {%- endfor %}
20 | flatDir {
21 | dirs 'libs'
22 | }
23 | }
24 | }
25 |
26 | {% if is_library %}
27 | apply plugin: 'com.android.library'
28 | {% else %}
29 | apply plugin: 'com.android.application'
30 | apply plugin: 'com.google.gms.google-services'
31 | {% endif %}
32 |
33 | android {
34 | compileSdkVersion {{ android_api }}
35 | buildToolsVersion '{{ build_tools_version }}'
36 | defaultConfig {
37 | minSdkVersion {{ args.min_sdk_version }}
38 | targetSdkVersion {{ android_api }}
39 | versionCode {{ args.numeric_version }}
40 | versionName '{{ args.version }}'
41 | manifestPlaceholders = {{ args.manifest_placeholders}}
42 | }
43 |
44 | {% if debug_build -%}
45 | packagingOptions {
46 | doNotStrip '**/*.so'
47 | }
48 | {%- endif %}
49 |
50 | {% if args.sign -%}
51 | signingConfigs {
52 | release {
53 | storeFile file(System.getenv("P4A_RELEASE_KEYSTORE"))
54 | keyAlias System.getenv("P4A_RELEASE_KEYALIAS")
55 | storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD")
56 | keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD")
57 | }
58 | }
59 |
60 | {%- endif %}
61 |
62 | {% if args.packaging_options -%}
63 | packagingOptions {
64 | {%- for option in args.packaging_options %}
65 | {{option}}
66 | {%- endfor %}
67 | }
68 | {%- endif %}
69 |
70 | buildTypes {
71 | debug {
72 | }
73 | release {
74 | {% if args.sign -%}
75 | signingConfig signingConfigs.release
76 | {%- endif %}
77 | }
78 | }
79 |
80 | compileOptions {
81 | {% if args.enable_androidx %}
82 | sourceCompatibility JavaVersion.VERSION_1_8
83 | targetCompatibility JavaVersion.VERSION_1_8
84 | {% else %}
85 | sourceCompatibility JavaVersion.VERSION_1_7
86 | targetCompatibility JavaVersion.VERSION_1_7
87 | {% endif %}
88 | {%- for option in args.compile_options %}
89 | {{option}}
90 | {%- endfor %}
91 | }
92 |
93 | sourceSets {
94 | main {
95 | jniLibs.srcDir 'libs'
96 | java {
97 |
98 | {%- for adir, pattern in args.extra_source_dirs -%}
99 | srcDir '{{adir}}'
100 | {%- endfor -%}
101 |
102 | }
103 | }
104 | }
105 |
106 | }
107 |
108 | dependencies {
109 | implementation platform('com.google.firebase:firebase-bom:27.1.0')
110 | {%- for aar in aars %}
111 | implementation(name: '{{ aar }}', ext: 'aar')
112 | {%- endfor -%}
113 | {%- for jar in jars %}
114 | implementation files('src/main/libs/{{ jar }}')
115 | {%- endfor -%}
116 | {%- if args.depends -%}
117 | {%- for depend in args.depends %}
118 | implementation '{{ depend }}'
119 | {%- endfor %}
120 | {%- endif %}
121 | {% if args.presplash_lottie %}
122 | implementation 'com.airbnb.android:lottie:3.4.0'
123 | {%- endif %}
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/src/python/buildozer.spec:
--------------------------------------------------------------------------------
1 | [app]
2 |
3 | # (str) Title of your application
4 | title = My Application
5 |
6 | # (str) Package name
7 | package.name = youtube
8 |
9 | # (str) Package domain (needed for android/ios packaging)
10 | package.domain = com.waterfall
11 |
12 | # (str) Source code where the main.py live
13 | source.dir = .
14 |
15 | # (list) Source files to include (let empty to include all the files)
16 | source.include_exts = py,png,jpg,kv,atlas
17 |
18 | # (list) List of inclusions using pattern matching
19 | #source.include_patterns = assets/*,images/*.png
20 |
21 | # (list) Source files to exclude (let empty to not exclude anything)
22 | #source.exclude_exts = spec
23 |
24 | # (list) List of directory to exclude (let empty to not exclude anything)
25 | #source.exclude_dirs = tests, bin, venv
26 |
27 | # (list) List of exclusions using pattern matching
28 | #source.exclude_patterns = license,images/*/*.jpg
29 |
30 | # (str) Application versioning (method 1)
31 | version = 0.1
32 |
33 | # (str) Application versioning (method 2)
34 | # version.regex = __version__ = ['"](.*)['"]
35 | # version.filename = %(source.dir)s/main.py
36 |
37 | # (list) Application requirements
38 | # comma separated e.g. requirements = sqlite3,kivy
39 | requirements = python3,kivy,requests, urllib3, chardet, certifi, idna
40 |
41 | # (str) Custom source folders for requirements
42 | # Sets custom source for any requirements with recipes
43 | # requirements.source.kivy = ../../kivy
44 |
45 | # (str) Presplash of the application
46 | #presplash.filename = %(source.dir)s/data/presplash.png
47 |
48 | # (str) Icon of the application
49 | #icon.filename = %(source.dir)s/data/icon.png
50 |
51 | # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
52 | orientation = portrait
53 |
54 | # (list) List of service to declare
55 | services = PythonNotificationHandler:python_notification_handler.py
56 |
57 | #
58 | # OSX Specific
59 | #
60 |
61 | #
62 | # author = © Copyright Info
63 |
64 | # change the major version of python used by the app
65 | osx.python_version = 3
66 |
67 | # Kivy version to use
68 | osx.kivy_version = 1.9.1
69 |
70 | #
71 | # Android specific
72 | #
73 |
74 | # (bool) Indicate if the application should be fullscreen or not
75 | fullscreen = 0
76 |
77 | # (string) Presplash background color (for android toolchain)
78 | # Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
79 | # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
80 | # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
81 | # olive, purple, silver, teal.
82 | #android.presplash_color = #FFFFFF
83 |
84 | # (string) Presplash animation using Lottie format.
85 | # see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
86 | # for general documentation.
87 | # Lottie files can be created using various tools, like Adobe After Effect or Synfig.
88 | #android.presplash_lottie = "path/to/lottie/file.json"
89 |
90 | # (list) Permissions
91 | #android.permissions = INTERNET
92 |
93 | # (list) features (adds uses-feature -tags to manifest)
94 | #android.features = android.hardware.usb.host
95 |
96 | # (int) Target Android API, should be as high as possible.
97 | android.api = 34
98 |
99 | # (int) Minimum API your APK will support.
100 | #android.minapi = 21
101 |
102 | # (int) Android SDK version to use
103 | #android.sdk = 20
104 |
105 | # (str) Android NDK version to use
106 | #android.ndk = 19b
107 |
108 | # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
109 | #android.ndk_api = 21
110 |
111 | # (bool) Use --private data storage (True) or --dir public storage (False)
112 | #android.private_storage = True
113 |
114 | # (str) Android NDK directory (if empty, it will be automatically downloaded.)
115 | #android.ndk_path =
116 |
117 | # (str) Android SDK directory (if empty, it will be automatically downloaded.)
118 | #android.sdk_path =
119 |
120 | # (str) ANT directory (if empty, it will be automatically downloaded.)
121 | #android.ant_path =
122 |
123 | # (bool) If True, then skip trying to update the Android sdk
124 | # This can be useful to avoid excess Internet downloads or save time
125 | # when an update is due and you just want to test/build your package
126 | # android.skip_update = False
127 |
128 | # (bool) If True, then automatically accept SDK license
129 | # agreements. This is intended for automation only. If set to False,
130 | # the default, you will be shown the license when first running
131 | # buildozer.
132 | android.accept_sdk_license = True
133 |
134 | # (str) Android entry point, default is ok for Kivy-based app
135 | #android.entrypoint = org.renpy.android.PythonActivity
136 |
137 | # (str) Android app theme, default is ok for Kivy-based app
138 | # android.apptheme = "@android:style/Theme.NoTitleBar"
139 |
140 | # (list) Pattern to whitelist for the whole project
141 | #android.whitelist =
142 |
143 | # (str) Path to a custom whitelist file
144 | #android.whitelist_src =
145 |
146 | # (str) Path to a custom blacklist file
147 | #android.blacklist_src =
148 |
149 | # (list) List of Java .jar files to add to the libs so that pyjnius can access
150 | # their classes. Don't add jars that you do not need, since extra jars can slow
151 | # down the build process. Allows wildcards matching, for example:
152 | # OUYA-ODK/libs/*.jar
153 | #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
154 |
155 | # (list) List of Java files to add to the android project (can be java or a
156 | # directory containing the files)
157 | android.add_src =libs/
158 |
159 | # (list) Android AAR archives to add
160 | #android.add_aars =
161 |
162 | # (list) Gradle dependencies to add
163 | android.gradle_dependencies =com.google.firebase:firebase-messaging,com.google.firebase:firebase-analytics,com.google.code.gson:gson:2.8.6
164 |
165 | # (list) add java compile options
166 | # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
167 | # see https://developer.android.com/studio/write/java8-support for further information
168 | # android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
169 |
170 | # (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
171 | # please enclose in double quotes
172 | # e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
173 | #android.add_gradle_repositories =
174 |
175 | # (list) packaging options to add
176 | # see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
177 | # can be necessary to solve conflicts in gradle_dependencies
178 | # please enclose in double quotes
179 | # e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
180 | #android.add_packaging_options =
181 |
182 | # (list) Java classes to add as activities to the manifest.
183 | #android.add_activities = com.example.ExampleActivity
184 |
185 | # (str) OUYA Console category. Should be one of GAME or APP
186 | # If you leave this blank, OUYA support will not be enabled
187 | #android.ouya.category = GAME
188 |
189 | # (str) Filename of OUYA Console icon. It must be a 732x412 png image.
190 | #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
191 |
192 | # (str) XML file to include as an intent filters in tag
193 | #android.manifest.intent_filters =
194 |
195 | # (str) launchMode to set for the main activity
196 | #android.manifest.launch_mode = standard
197 |
198 | # (list) Android additional libraries to copy into libs/armeabi
199 | #android.add_libs_armeabi = libs/android/*.so
200 | #android.add_libs_armeabi_v7a = libs/android-v7/*.so
201 | #android.add_libs_arm64_v8a = libs/android-v8/*.so
202 | #android.add_libs_x86 = libs/android-x86/*.so
203 | #android.add_libs_mips = libs/android-mips/*.so
204 |
205 | # (bool) Indicate whether the screen should stay on
206 | # Don't forget to add the WAKE_LOCK permission if you set this to True
207 | #android.wakelock = False
208 |
209 | # (list) Android application meta-data to set (key=value format)
210 | #android.meta_data =
211 |
212 | # (list) Android library project to add (will be added in the
213 | # project.properties automatically.)
214 | #android.library_references =
215 |
216 | # (list) Android shared libraries which will be added to AndroidManifest.xml using tag
217 | #android.uses_library =
218 |
219 | # (str) Android logcat filters to use
220 | #android.logcat_filters = *:S python:D
221 |
222 | # (bool) Copy library instead of making a libpymodules.so
223 | #android.copy_libs = 1
224 |
225 | # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
226 | android.archs = arm64-v8a
227 |
228 | # (int) overrides automatic versionCode computation (used in build.gradle)
229 | # this is not the same as app version and should only be edited if you know what you're doing
230 | # android.numeric_version = 1
231 |
232 | # (bool) enables Android auto backup feature (Android API >=23)
233 | android.allow_backup = True
234 |
235 | # (str) XML file for custom backup rules (see official auto backup documentation)
236 | # android.backup_rules =
237 |
238 | # (str) If you need to insert variables into your AndroidManifest.xml file,
239 | # you can do so with the manifestPlaceholders property.
240 | # This property takes a map of key-value pairs. (via a string)
241 | # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
242 | # android.manifest_placeholders = [:]
243 |
244 | #
245 | # Python for android (p4a) specific
246 | #
247 |
248 | # (str) python-for-android fork to use, defaults to upstream (kivy)
249 | #p4a.fork = kivy
250 |
251 | # (str) python-for-android branch to use, defaults to master
252 | #p4a.branch = develop
253 |
254 | # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
255 | p4a.source_dir =/Users/thomas/Documents/projects/push_mac/python-for-android
256 |
257 | # (str) The directory in which python-for-android should look for your own build recipes (if any)
258 | #p4a.local_recipes =
259 |
260 | # (str) Filename to the hook for p4a
261 | #p4a.hook =
262 |
263 | # (str) Bootstrap to use for android builds
264 | # p4a.bootstrap = sdl2
265 |
266 | # (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
267 | #p4a.port =
268 |
269 | # Control passing the --use-setup-py vs --ignore-setup-py to p4a
270 | # "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
271 | # Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
272 | # NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
273 | # setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
274 | #p4a.setup_py = false
275 |
276 |
277 | #
278 | # iOS specific
279 | #
280 |
281 | # (str) Path to a custom kivy-ios folder
282 | #ios.kivy_ios_dir = ../kivy-ios
283 | # Alternately, specify the URL and branch of a git checkout:
284 | ios.kivy_ios_url = https://github.com/kivy/kivy-ios
285 | ios.kivy_ios_branch = master
286 |
287 | # Another platform dependency: ios-deploy
288 | # Uncomment to use a custom checkout
289 | #ios.ios_deploy_dir = ../ios_deploy
290 | # Or specify URL and branch
291 | ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
292 | ios.ios_deploy_branch = 1.10.0
293 |
294 | # (bool) Whether or not to sign the code
295 | ios.codesign.allowed = false
296 |
297 | # (str) Name of the certificate to use for signing the debug version
298 | # Get a list of available identities: buildozer ios list_identities
299 | #ios.codesign.debug = "iPhone Developer: ()"
300 |
301 | # (str) Name of the certificate to use for signing the release version
302 | #ios.codesign.release = %(ios.codesign.debug)s
303 |
304 |
305 | [buildozer]
306 |
307 | # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
308 | log_level = 2
309 |
310 | # (int) Display warning if buildozer is run as root (0 = False, 1 = True)
311 | warn_on_root = 1
312 |
313 | # (str) Path to build artifact storage, absolute or relative to spec file
314 | # build_dir = ./.buildozer
315 |
316 | # (str) Path to build output (i.e. .apk, .ipa) storage
317 | # bin_dir = ./bin
318 |
319 | # -----------------------------------------------------------------------------
320 | # List as sections
321 | #
322 | # You can define all the "list" as [section:key].
323 | # Each line will be considered as a option to the list.
324 | # Let's take [app] / source.exclude_patterns.
325 | # Instead of doing:
326 | #
327 | #[app]
328 | #source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
329 | #
330 | # This can be translated into:
331 | #
332 | #[app:source.exclude_patterns]
333 | #license
334 | #data/audio/*.wav
335 | #data/images/original/*
336 | #
337 |
338 |
339 | # -----------------------------------------------------------------------------
340 | # Profiles
341 | #
342 | # You can extend section / key with a profile
343 | # For example, you want to deploy a demo version of your application without
344 | # HD content. You could first change the title to add "(demo)" in the name
345 | # and extend the excluded directories to remove the HD content.
346 | #
347 | #[app@demo]
348 | #title = My Application (demo)
349 | #
350 | #[app:source.exclude_patterns@demo]
351 | #images/hd/*
352 | #
353 | # Then, invoke the command line with the "demo" profile:
354 | #
355 | #buildozer --profile demo android debug
356 |
--------------------------------------------------------------------------------
/src/python/libs/ContextHolder.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 | import android.content.Context;
5 | import android.util.Log;
6 |
7 | public class ContextHolder {
8 | private static Context applicationContext;
9 |
10 | public static Context getApplicationContext() {
11 | return applicationContext;
12 | }
13 |
14 | public static void setApplicationContext(Context applicationContext) {
15 | Log.d("KVFireContextHolder", "received application context.");
16 | ContextHolder.applicationContext = applicationContext;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/python/libs/KivyFirebaseMessagingBackgroundExecutor.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 | import android.util.Log;
5 |
6 | import java.util.concurrent.atomic.AtomicBoolean;
7 |
8 | public class KivyFirebaseMessagingBackgroundExecutor {
9 | private static AtomicBoolean started = new AtomicBoolean(false);
10 | public static void startBackgroundPythonService() {
11 |
12 | Log.d("BackgroundExecutor", "Starting background service");
13 | com.waterfall.youtube.ServicePythonnotificationhandler.start(ContextHolder.getApplicationContext(), "");
14 | Log.d("BackgroundExecutor", "Background service started");
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/python/libs/KivyFirebaseMessagingBackgroundService.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 |
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.os.Handler;
8 | import android.util.Log;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.core.app.JobIntentService;
12 |
13 | import com.google.firebase.messaging.RemoteMessage;
14 |
15 | import java.util.Map;
16 | import java.util.concurrent.CountDownLatch;
17 |
18 | public class KivyFirebaseMessagingBackgroundService extends JobIntentService {
19 | private static final String TAG = "KVFireMsgService";
20 |
21 | /**
22 | * Schedule the message to be handled by the {@link KivyFirebaseMessagingBackgroundService}.
23 | */
24 | public static void enqueueMessageProcessing(Context context, Intent messageIntent) {
25 | enqueueWork(
26 | context,
27 | KivyFirebaseMessagingBackgroundService.class,
28 | KivyFirebaseMessagingUtils.JOB_ID,
29 | messageIntent);
30 | }
31 |
32 | @Override
33 | public void onCreate() {
34 | super.onCreate();
35 | KivyFirebaseMessagingBackgroundExecutor.startBackgroundPythonService();
36 | }
37 |
38 | @Override
39 | protected void onHandleWork(@NonNull final Intent intent) {
40 |
41 |
42 | // There were no pre-existing callback requests. Execute the callback
43 | // specified by the incoming intent.
44 | final CountDownLatch latch = new CountDownLatch(1);
45 |
46 | new Handler(getMainLooper())
47 | .post(new Runnable() {
48 | @Override
49 | public void run() {
50 | RemoteMessage remoteMessage =
51 | intent.getParcelableExtra(KivyFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE);
52 | if (remoteMessage != null) {
53 | Map remoteMessageMap =
54 | KivyFirebaseMessagingUtils.remoteMessageToMap(remoteMessage);
55 | remoteMessageMap.put("unique_key", Math.random() + "");
56 | PlatformIntermediate.addbackroundMessage(remoteMessageMap, ContextHolder.getApplicationContext());
57 | }
58 | // End
59 | latch.countDown();
60 | }
61 | }
62 | );
63 |
64 | try {
65 | latch.await();
66 | } catch (InterruptedException ex) {
67 | Log.i(TAG, "Exception waiting to execute Python callback", ex);
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/python/libs/KivyFirebaseMessagingReceiver.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 |
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.util.Log;
9 | import com.google.firebase.messaging.RemoteMessage;
10 | import java.util.HashMap;
11 |
12 |
13 | public class KivyFirebaseMessagingReceiver extends BroadcastReceiver {
14 | private static final String TAG = "FLTFireMsgReceiver";
15 | static HashMap notifications = new HashMap<>();
16 |
17 | @Override
18 | public void onReceive(Context context, Intent intent) {
19 | Log.d(TAG, "broadcast received for message");
20 | if (ContextHolder.getApplicationContext() == null) {
21 | ContextHolder.setApplicationContext(context.getApplicationContext());
22 | }
23 |
24 | RemoteMessage remoteMessage = new RemoteMessage(intent.getExtras());
25 |
26 | // Store the RemoteMessage if the message contains a notification payload.
27 | if (remoteMessage.getNotification() != null) {
28 | notifications.put(remoteMessage.getMessageId(), remoteMessage);
29 | KivyFirebaseMessagingStore.getInstance().storeFirebaseMessage(remoteMessage);
30 | }
31 |
32 | // |-> ---------------------
33 | // App in Foreground
34 | // ------------------------
35 | if (KivyFirebaseMessagingUtils.isApplicationForeground(context)) {
36 | Log.d(TAG, "Setting the foreground.");
37 | if(remoteMessage.getNotification() != null){
38 | Log.d("BTW, title is " + remoteMessage.getNotification().getTitle());
39 | }
40 | PlatformIntermediate.setForegroundMessage(KivyFirebaseMessagingUtils.remoteMessageToMap(remoteMessage));
41 | return;
42 | }
43 |
44 | // |-> ---------------------
45 | // App in Background/Quit
46 | // ------------------------
47 | Log.d(TAG, "App in Background/Quit");
48 | // HashMap payload = new HashMap<>();
49 | // payload.put("unique_key", Math.random());
50 | // payload.put("payload_type", "BACKGROUND_MSG");
51 | // payload.put("data", KivyFirebaseMessagingUtils.remoteMessageToMap(remoteMessage));
52 | // Gson gson = new Gson();
53 | // String json = gson.toJson(payload);
54 | // backgroundMessages.put(Math.random()+"",json);
55 | // com.waterfall.youtube.ServicePythonnotificationhandler.start(org.kivy.android.PythonActivity.mActivity, json);
56 | // Issue with above is it relies on the Python service to be not running. Moment it's already running and you try starting
57 | // it, Android won't allow that.
58 |
59 | Intent onBackgroundMessageIntent =
60 | new Intent(context, org.kivy.plugins.messaging.KivyFirebaseMessagingBackgroundService.class);
61 | onBackgroundMessageIntent.putExtra(
62 | KivyFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE, remoteMessage);
63 | org.kivy.plugins.messaging.KivyFirebaseMessagingBackgroundService.enqueueMessageProcessing(
64 | context, onBackgroundMessageIntent);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/python/libs/KivyFirebaseMessagingService.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 |
5 |
6 | import androidx.annotation.NonNull;
7 |
8 | import com.google.firebase.messaging.FirebaseMessagingService;
9 | import com.google.firebase.messaging.RemoteMessage;
10 |
11 | public class KivyFirebaseMessagingService extends FirebaseMessagingService {
12 | @Override
13 | public void onNewToken(@NonNull String token) {
14 | PlatformIntermediate.token = token;
15 | }
16 |
17 | @Override
18 | public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
19 | // Added for commenting purposes;
20 | // We don't handle the message here as we already handle it in the receiver and don't want to duplicate.
21 | }
22 | }
--------------------------------------------------------------------------------
/src/python/libs/KivyFirebaseMessagingStore.java:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Chromium Authors. All rights reserved.
2 | package org.kivy.plugins.messaging;
3 |
4 |
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import com.google.firebase.messaging.RemoteMessage;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.HashMap;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import java.util.Map;
14 | import org.json.JSONArray;
15 | import org.json.JSONException;
16 | import org.json.JSONObject;
17 |
18 | public class KivyFirebaseMessagingStore {
19 | private static final String PREFERENCES_FILE = "io.flutter.plugins.firebase.messaging";
20 | private static final String KEY_NOTIFICATION_IDS = "notification_ids";
21 | private static final int MAX_SIZE_NOTIFICATIONS = 20;
22 | private static KivyFirebaseMessagingStore instance;
23 | private final String DELIMITER = ",";
24 | private SharedPreferences preferences;
25 |
26 | public static KivyFirebaseMessagingStore getInstance() {
27 | if (instance == null) {
28 | instance = new KivyFirebaseMessagingStore();
29 | }
30 | return instance;
31 | }
32 |
33 | private SharedPreferences getPreferences() {
34 | if (preferences == null) {
35 | preferences =
36 | ContextHolder.getApplicationContext()
37 | .getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
38 | }
39 | return preferences;
40 | }
41 |
42 | public void setPreferencesStringValue(String key, String value) {
43 | getPreferences().edit().putString(key, value).apply();
44 | }
45 |
46 | public String getPreferencesStringValue(String key, String defaultValue) {
47 | return getPreferences().getString(key, defaultValue);
48 | }
49 |
50 | public void storeFirebaseMessage(RemoteMessage remoteMessage) {
51 | String remoteMessageString =
52 | new JSONObject(KivyFirebaseMessagingUtils.remoteMessageToMap(remoteMessage)).toString();
53 | setPreferencesStringValue(remoteMessage.getMessageId(), remoteMessageString);
54 |
55 | // Save new notification id.
56 | // Note that this is using a comma delimited string to preserve ordering. We could use a String Set
57 | // on SharedPreferences but this won't guarantee ordering when we want to remove the oldest added ids.
58 | String notifications = getPreferencesStringValue(KEY_NOTIFICATION_IDS, "");
59 | notifications += remoteMessage.getMessageId() + DELIMITER; // append to last
60 |
61 | // Check and remove old notification messages.
62 | List allNotificationList =
63 | new ArrayList<>(Arrays.asList(notifications.split(DELIMITER)));
64 | if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) {
65 | String firstRemoteMessageId = allNotificationList.get(0);
66 | getPreferences().edit().remove(firstRemoteMessageId).apply();
67 | notifications = notifications.replace(firstRemoteMessageId + DELIMITER, "");
68 | }
69 |
70 | setPreferencesStringValue(KEY_NOTIFICATION_IDS, notifications);
71 | }
72 |
73 | public RemoteMessage getFirebaseMessage(String remoteMessageId) {
74 | String remoteMessageString = getPreferencesStringValue(remoteMessageId, null);
75 | if (remoteMessageString != null) {
76 | try {
77 | Map argumentsMap = new HashMap<>(1);
78 | Map messageOutMap = jsonObjectToMap(new JSONObject(remoteMessageString));
79 | // Add a fake 'to' - as it's required to construct a RemoteMessage instance.
80 | messageOutMap.put("to", remoteMessageId);
81 | argumentsMap.put("message", messageOutMap);
82 | return KivyFirebaseMessagingUtils.getRemoteMessageForArguments(argumentsMap);
83 | } catch (JSONException e) {
84 | e.printStackTrace();
85 | }
86 | }
87 | return null;
88 | }
89 |
90 | public void removeFirebaseMessage(String remoteMessageId) {
91 | getPreferences().edit().remove(remoteMessageId).apply();
92 | String notifications = getPreferencesStringValue(KEY_NOTIFICATION_IDS, "");
93 | if (!notifications.isEmpty()) {
94 | notifications = notifications.replace(remoteMessageId + DELIMITER, "");
95 | setPreferencesStringValue(KEY_NOTIFICATION_IDS, notifications);
96 | }
97 | }
98 |
99 | private Map jsonObjectToMap(JSONObject jsonObject) throws JSONException {
100 | Map map = new HashMap<>();
101 | Iterator keys = jsonObject.keys();
102 | while (keys.hasNext()) {
103 | String key = keys.next();
104 | Object value = jsonObject.get(key);
105 | if (value instanceof JSONArray) {
106 | value = jsonArrayToList((JSONArray) value);
107 | } else if (value instanceof JSONObject) {
108 | value = jsonObjectToMap((JSONObject) value);
109 | }
110 | map.put(key, value);
111 | }
112 | return map;
113 | }
114 |
115 | public List