android-widgets 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {android_widgets-0.1.0 → android_widgets-0.1.2}/PKG-INFO +17 -13
- {android_widgets-0.1.0 → android_widgets-0.1.2}/README.md +11 -7
- android_widgets-0.1.2/android_widgets/__init__.py +206 -0
- android_widgets-0.1.2/android_widgets/aw_logging.py +58 -0
- android_widgets-0.1.2/android_widgets/config.py +74 -0
- android_widgets-0.1.2/android_widgets/facade.py +376 -0
- android_widgets-0.1.2/android_widgets/java_classes.py +11 -0
- android_widgets-0.1.2/android_widgets/maker.py +129 -0
- android_widgets-0.1.2/android_widgets/tools.py +43 -0
- {android_widgets-0.1.0 → android_widgets-0.1.2}/android_widgets.egg-info/PKG-INFO +17 -13
- android_widgets-0.1.2/android_widgets.egg-info/SOURCES.txt +14 -0
- android_widgets-0.1.2/android_widgets.egg-info/top_level.txt +3 -0
- {android_widgets-0.1.0 → android_widgets-0.1.2}/pyproject.toml +6 -6
- android_widgets-0.1.0/android-widgets/__init__.py +0 -5
- android_widgets-0.1.0/android_widgets.egg-info/SOURCES.txt +0 -8
- android_widgets-0.1.0/android_widgets.egg-info/top_level.txt +0 -3
- {android_widgets-0.1.0 → android_widgets-0.1.2}/android_widgets.egg-info/dependency_links.txt +0 -0
- {android_widgets-0.1.0 → android_widgets-0.1.2}/android_widgets.egg-info/requires.txt +0 -0
- {android_widgets-0.1.0 → android_widgets-0.1.2}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: android-widgets
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: This would be used to auto gen XMLs for Android Home Screen widgets.
|
|
5
5
|
Author-email: Fabian <fector101@yahoo.com>
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
-
Project-URL: Source, https://github.com/
|
|
10
|
-
Project-URL: Tracker, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/Fector101/kivy-androidwidgets
|
|
8
|
+
Project-URL: Documentation, https://github.com/Fector101/kivy-androidwidgets
|
|
9
|
+
Project-URL: Source, https://github.com/Fector101/kivy-androidwidgets
|
|
10
|
+
Project-URL: Tracker, https://github.com/Fector101/kivy-androidwidgets/issues
|
|
11
11
|
Keywords: kivy,android,widgets,hello-world,python-package
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -27,7 +27,7 @@ Complete Sample: [working app](https://github.com/Fector101/wallpaper-carousel)
|
|
|
27
27
|
5 Steps For a simple widget
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
30
|
+
### step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
31
31
|
Store it in: `res/layout/simple_widget.xml`
|
|
32
32
|
This a simple widget with a text
|
|
33
33
|
```xml
|
|
@@ -50,7 +50,7 @@ This a simple widget with a text
|
|
|
50
50
|
|
|
51
51
|
</LinearLayout>
|
|
52
52
|
```
|
|
53
|
-
step 2: Create an xml containing the info about the widget.
|
|
53
|
+
### step 2: Create an xml containing the info about the widget.
|
|
54
54
|
Like: size, preview icon and others
|
|
55
55
|
path: `res/xml/widgetproviderinfo.xml`
|
|
56
56
|
```xml
|
|
@@ -66,7 +66,7 @@ path: `res/xml/widgetproviderinfo.xml`
|
|
|
66
66
|
```
|
|
67
67
|
Create preview image png in right path`res/drawable/ic_launcher_foreground.png`
|
|
68
68
|
|
|
69
|
-
step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
69
|
+
### step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
70
70
|
path: `src/SimpleWidget.java`.
|
|
71
71
|
This will receive an event when widget is add to change it's text
|
|
72
72
|
|
|
@@ -99,7 +99,8 @@ public class SimpleWidget extends AppWidgetProvider {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
### Step 4: Automate injecting Receiver in XML
|
|
103
104
|
|
|
104
105
|
path:`p4a/hook.py`
|
|
105
106
|
|
|
@@ -138,16 +139,19 @@ def after_apk_build(toolchain: ToolchainCL):
|
|
|
138
139
|
print("Successfully_101: Manifest update completed successfully!")
|
|
139
140
|
|
|
140
141
|
```
|
|
141
|
-
|
|
142
|
+
|
|
143
|
+
### Step 5: From `buildozer.spec` tell it you want to add resources, src and p4a hook
|
|
142
144
|
```ini
|
|
143
145
|
android.add_resources = res
|
|
144
146
|
android.add_src = src
|
|
145
147
|
p4a.hook = p4a/hook.py
|
|
146
148
|
```
|
|
147
149
|
|
|
148
|
-
For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
149
|
-
---
|
|
150
150
|
|
|
151
151
|
Sample Image:
|
|
152
152
|
|
|
153
153
|

|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
157
|
+
|
|
@@ -6,7 +6,7 @@ Complete Sample: [working app](https://github.com/Fector101/wallpaper-carousel)
|
|
|
6
6
|
5 Steps For a simple widget
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
9
|
+
### step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
10
10
|
Store it in: `res/layout/simple_widget.xml`
|
|
11
11
|
This a simple widget with a text
|
|
12
12
|
```xml
|
|
@@ -29,7 +29,7 @@ This a simple widget with a text
|
|
|
29
29
|
|
|
30
30
|
</LinearLayout>
|
|
31
31
|
```
|
|
32
|
-
step 2: Create an xml containing the info about the widget.
|
|
32
|
+
### step 2: Create an xml containing the info about the widget.
|
|
33
33
|
Like: size, preview icon and others
|
|
34
34
|
path: `res/xml/widgetproviderinfo.xml`
|
|
35
35
|
```xml
|
|
@@ -45,7 +45,7 @@ path: `res/xml/widgetproviderinfo.xml`
|
|
|
45
45
|
```
|
|
46
46
|
Create preview image png in right path`res/drawable/ic_launcher_foreground.png`
|
|
47
47
|
|
|
48
|
-
step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
48
|
+
### step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
49
49
|
path: `src/SimpleWidget.java`.
|
|
50
50
|
This will receive an event when widget is add to change it's text
|
|
51
51
|
|
|
@@ -78,7 +78,8 @@ public class SimpleWidget extends AppWidgetProvider {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
### Step 4: Automate injecting Receiver in XML
|
|
82
83
|
|
|
83
84
|
path:`p4a/hook.py`
|
|
84
85
|
|
|
@@ -117,16 +118,19 @@ def after_apk_build(toolchain: ToolchainCL):
|
|
|
117
118
|
print("Successfully_101: Manifest update completed successfully!")
|
|
118
119
|
|
|
119
120
|
```
|
|
120
|
-
|
|
121
|
+
|
|
122
|
+
### Step 5: From `buildozer.spec` tell it you want to add resources, src and p4a hook
|
|
121
123
|
```ini
|
|
122
124
|
android.add_resources = res
|
|
123
125
|
android.add_src = src
|
|
124
126
|
p4a.hook = p4a/hook.py
|
|
125
127
|
```
|
|
126
128
|
|
|
127
|
-
For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
128
|
-
---
|
|
129
129
|
|
|
130
130
|
Sample Image:
|
|
131
131
|
|
|
132
132
|

|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
136
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
from .config import is_platform_android
|
|
6
|
+
from .aw_logging import logger, enable_logging
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__ = ["logger"]
|
|
10
|
+
|
|
11
|
+
from .config import get_python_activity_context
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from .java_classes import *
|
|
15
|
+
except Exception as e:
|
|
16
|
+
print("[android_widgets] Import Java classes Error:", e)
|
|
17
|
+
from .facade import *
|
|
18
|
+
|
|
19
|
+
RemoteViews_ = RemoteViews
|
|
20
|
+
AppWidgetManager_ = AppWidgetManager
|
|
21
|
+
|
|
22
|
+
from .tools import SpecFile
|
|
23
|
+
|
|
24
|
+
if is_platform_android():
|
|
25
|
+
enable_logging()
|
|
26
|
+
|
|
27
|
+
def get_resources():
|
|
28
|
+
context = get_python_activity_context()
|
|
29
|
+
return context.getResources()
|
|
30
|
+
|
|
31
|
+
def get_package_name():
|
|
32
|
+
context = get_python_activity_context()
|
|
33
|
+
return context.getPackageName() # package.domain + "." + package.name
|
|
34
|
+
|
|
35
|
+
def get_resource(name, folder_base):
|
|
36
|
+
resources = get_resources()
|
|
37
|
+
package_name = get_package_name()
|
|
38
|
+
print("resources.getIdentifier",name, folder_base,"id:",resources.getIdentifier(name, folder_base, package_name))
|
|
39
|
+
return resources.getIdentifier(name, folder_base, package_name)
|
|
40
|
+
|
|
41
|
+
def get_buildozer_spec_file_path():
|
|
42
|
+
return os.path.join(os.getcwd(), "buildozer.spec")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Layout:
|
|
46
|
+
def __init__(self, layout_name):
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
:param layout_name: base name without '.xml'
|
|
50
|
+
"""
|
|
51
|
+
self.layout_name = layout_name
|
|
52
|
+
self.id = get_resource(layout_name, "layout")
|
|
53
|
+
self.check_if_layout_exists()
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def layout_path(self):
|
|
57
|
+
specFile = SpecFile(get_buildozer_spec_file_path())
|
|
58
|
+
resources_folder = specFile.get("app", "android.add_resources", '')
|
|
59
|
+
layout_path = os.path.join(resources_folder, 'layout', self.layout_name + '.xml')
|
|
60
|
+
return layout_path
|
|
61
|
+
|
|
62
|
+
def check_if_layout_exists(self):
|
|
63
|
+
if is_platform_android():
|
|
64
|
+
if not self.id:
|
|
65
|
+
logger.error(f"Layout doesn't exist: {self.layout_name}")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
spec_file_path = get_buildozer_spec_file_path()
|
|
69
|
+
# This not a real path on android /data/data/org.wally.waller/files/app/buildozer.spec
|
|
70
|
+
|
|
71
|
+
if not os.path.exists(spec_file_path):
|
|
72
|
+
logger.debug(f"buildozer.spec file not found at: {spec_file_path}")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
if not os.path.exists(self.layout_path):
|
|
76
|
+
logger.error(f"Layout doesn't exist: {self.layout_path}")
|
|
77
|
+
return None
|
|
78
|
+
else:
|
|
79
|
+
logger.info(f"Found Layout: {self.layout_path}")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RemoteViews:
|
|
84
|
+
def __init__(self, layout:Layout):
|
|
85
|
+
self.layout = layout
|
|
86
|
+
layout_id = layout.id
|
|
87
|
+
package_name = get_package_name()
|
|
88
|
+
self.main = RemoteViews_(package_name, layout_id)
|
|
89
|
+
|
|
90
|
+
def setTextViewText(self, text_id, text):
|
|
91
|
+
resources = get_resources()
|
|
92
|
+
package_name = get_package_name()
|
|
93
|
+
__text_id = resources.getIdentifier(text_id, "id",package_name)
|
|
94
|
+
# print("text_id:", __text_id)
|
|
95
|
+
if not __text_id:
|
|
96
|
+
logger.error(f"Text ID doesn't exist: No @+id/{text_id} in res/layout/{self.layout.layout_name}.xml")
|
|
97
|
+
return None
|
|
98
|
+
self.main.setTextViewText(__text_id, String(text))
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AppWidgetManager:
|
|
103
|
+
def __init__(self, java_class_name):
|
|
104
|
+
self.java_class_name = java_class_name
|
|
105
|
+
context = get_python_activity_context()
|
|
106
|
+
package_name = get_package_name()
|
|
107
|
+
component = ComponentName(context, f'{package_name}.{java_class_name}')
|
|
108
|
+
self.main = AppWidgetManager_.getInstance(context)
|
|
109
|
+
self.widget_ids = self.main.getAppWidgetIds(component)
|
|
110
|
+
self.check_if_java_file_exists()
|
|
111
|
+
|
|
112
|
+
def updateAppWidget(self, java_view_object):
|
|
113
|
+
self.main.updateAppWidget(self.widget_ids, java_view_object)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def java_file_path(self):
|
|
117
|
+
specFile = SpecFile(get_buildozer_spec_file_path())
|
|
118
|
+
jave_src_folder = specFile.get("app", "android.add_src", '')
|
|
119
|
+
java_file_path__ = os.path.join(jave_src_folder, self.java_class_name + '.java')
|
|
120
|
+
return java_file_path__
|
|
121
|
+
|
|
122
|
+
def check_if_java_file_exists(self):
|
|
123
|
+
spec_file_path = get_buildozer_spec_file_path()
|
|
124
|
+
if is_platform_android():
|
|
125
|
+
if not self.widget_ids: # when wrong .java file name is given self.widget_ids == []
|
|
126
|
+
logger.warning(f"Java File might not exist: {self.java_class_name}.java")
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if not os.path.exists(spec_file_path):
|
|
130
|
+
logger.debug(f"buildozer.spec file not found at: {spec_file_path}")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
java_file_path = self.java_file_path
|
|
134
|
+
if not os.path.exists(java_file_path):
|
|
135
|
+
logger.error(f"Java File doesn't exist: {java_file_path}")
|
|
136
|
+
# TODO Write emtpy java file
|
|
137
|
+
return None
|
|
138
|
+
else:
|
|
139
|
+
logger.info(f"Found Layout: {java_file_path}")
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
# try:
|
|
143
|
+
# print("-"*5,'test 1','-'*5)
|
|
144
|
+
# appWidgetManager = AppWidgetManager("Image")
|
|
145
|
+
# print("The widget_ids:", appWidgetManager.widget_ids)
|
|
146
|
+
#
|
|
147
|
+
# text_layout = Layout("image_test_widget")
|
|
148
|
+
# views = RemoteViews(layout=text_layout)
|
|
149
|
+
# views.setTextViewText(text_id="widget_text", text="Frm py 1")
|
|
150
|
+
#
|
|
151
|
+
# appWidgetManager.updateAppWidget(java_view_object=views.main)
|
|
152
|
+
# except Exception as e:
|
|
153
|
+
# print("-"*5,"test 1 Error:",e,"-"*5)
|
|
154
|
+
# traceback.print_exc()
|
|
155
|
+
#
|
|
156
|
+
# try:
|
|
157
|
+
# print("-"*5,'test 2','-'*5)
|
|
158
|
+
#
|
|
159
|
+
# appWidgetManager = AppWidgetManager("Image1")
|
|
160
|
+
# print("The widget_ids:", appWidgetManager.widget_ids)
|
|
161
|
+
#
|
|
162
|
+
# text_layout = Layout("image_test_widget1")
|
|
163
|
+
# views = RemoteViews(layout=text_layout)
|
|
164
|
+
# views.setTextViewText(text_id="widget_text", text="Frm py 2")
|
|
165
|
+
#
|
|
166
|
+
# appWidgetManager.updateAppWidget(java_view_object=views.main)
|
|
167
|
+
# except Exception as e:
|
|
168
|
+
# print("-" * 5, "test 2 Error:",e, "-" * 5)
|
|
169
|
+
# traceback.print_exc()
|
|
170
|
+
#
|
|
171
|
+
# try:
|
|
172
|
+
# print("-" * 5, 'test 3', '-' * 5)
|
|
173
|
+
#
|
|
174
|
+
# appWidgetManager = AppWidgetManager("Image1")
|
|
175
|
+
# print("The widget_ids:", appWidgetManager.widget_ids)
|
|
176
|
+
#
|
|
177
|
+
# text_layout = Layout("image_test_widget")
|
|
178
|
+
# views = RemoteViews(layout=text_layout)
|
|
179
|
+
# views.setTextViewText(text_id="widget_text3", text="Frm py 3")
|
|
180
|
+
#
|
|
181
|
+
# appWidgetManager.updateAppWidget(java_view_object=views.main)
|
|
182
|
+
# except Exception as e:
|
|
183
|
+
# print("-" * 5, "test 3 Error:",e, "-" * 5)
|
|
184
|
+
# traceback.print_exc()
|
|
185
|
+
|
|
186
|
+
# For Reference
|
|
187
|
+
# AppWidgetManager = autoclass('android.appwidget.AppWidgetManager')
|
|
188
|
+
# ComponentName = autoclass('android.content.ComponentName')
|
|
189
|
+
# RemoteViews = autoclass('android.widget.RemoteViews')
|
|
190
|
+
#
|
|
191
|
+
# context = get_python_activity_context() # PythonActivity.mActivity.getApplicationContext()
|
|
192
|
+
# resources = context.getResources()
|
|
193
|
+
# package_name = context.getPackageName()
|
|
194
|
+
#
|
|
195
|
+
# # IMPORTANT: use CLASS NAME STRING, NOT autoclass
|
|
196
|
+
# component = ComponentName( context, 'org.wally.waller.Image1' )
|
|
197
|
+
#
|
|
198
|
+
# appWidgetManager = AppWidgetManager.getInstance(context)
|
|
199
|
+
# ids = appWidgetManager.getAppWidgetIds(component)
|
|
200
|
+
#
|
|
201
|
+
# text_layout = resources.getIdentifier("image_test_widget", "layout", package_name)
|
|
202
|
+
# title_id = resources.getIdentifier("widget_text", "id", package_name)
|
|
203
|
+
#
|
|
204
|
+
# views = RemoteViews(package_name, text_layout)
|
|
205
|
+
# views.setTextViewText(title_id, AndroidString("Madness"))
|
|
206
|
+
# appWidgetManager.updateAppWidget(ids, views)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger("android_widgets")
|
|
5
|
+
logger.addHandler(logging.NullHandler())
|
|
6
|
+
# logger.propagate = False #The point of package is to help developing with python on PC, so logs very Useful
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ANSI color codes
|
|
10
|
+
COLORS = {
|
|
11
|
+
"DEBUG": "\033[36m", # Cyan
|
|
12
|
+
"INFO": "\033[32m", # Green
|
|
13
|
+
"WARNING": "\033[33m", # Yellow
|
|
14
|
+
"ERROR": "\033[31m", # Red
|
|
15
|
+
"CRITICAL": "\033[41m", # Red background
|
|
16
|
+
}
|
|
17
|
+
RESET = "\033[0m"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BracketColorFormatter(logging.Formatter):
|
|
21
|
+
def format(self, record):
|
|
22
|
+
# Fixed-width levelname
|
|
23
|
+
levelname = f"{record.levelname:<7}"
|
|
24
|
+
color = COLORS.get(record.levelname, "")
|
|
25
|
+
level_colored = f"{color}{levelname}{RESET}"
|
|
26
|
+
|
|
27
|
+
# Fixed-width logger name
|
|
28
|
+
name_fixed = f"{record.name:<15}"
|
|
29
|
+
|
|
30
|
+
# Build the final string manually to avoid any extra spaces
|
|
31
|
+
return f"[{level_colored}][{name_fixed}] {record.getMessage()}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def enable_logging(level=logging.DEBUG):
|
|
35
|
+
"""Enable colored logging for android_widgets logger."""
|
|
36
|
+
# Avoid adding multiple StreamHandlers
|
|
37
|
+
if any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
handler = logging.StreamHandler()
|
|
41
|
+
handler.setFormatter(BracketColorFormatter())
|
|
42
|
+
logger.addHandler(handler)
|
|
43
|
+
logger.setLevel(level)
|
|
44
|
+
|
|
45
|
+
def disable_logging():
|
|
46
|
+
"""Disable colored logging for android_widgets logger."""
|
|
47
|
+
# Remove all StreamHandlers (the ones we added in enable_colored_logging)
|
|
48
|
+
for handler in logger.handlers[:]:
|
|
49
|
+
if isinstance(handler, logging.StreamHandler):
|
|
50
|
+
logger.removeHandler(handler)
|
|
51
|
+
# Ensure logger stays silent
|
|
52
|
+
logger.addHandler(logging.NullHandler())
|
|
53
|
+
logger.setLevel(logging.NOTSET)
|
|
54
|
+
|
|
55
|
+
# ------------------ Test ------------------
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
enable_logging()
|
|
58
|
+
logger.info("Found Layout: app_src/android/res/layout/image_test_widget.xml")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from jnius import autoclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
ON_ANDROID = False
|
|
6
|
+
def is_platform_android():
|
|
7
|
+
# Took this from kivy to fix my logs in P4A.hook, so no need to import things i don't need by doing `from kivy.utils import platform`
|
|
8
|
+
if os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME"):
|
|
9
|
+
return True
|
|
10
|
+
kivy_build = os.environ.get('KIVY_BUILD', '')
|
|
11
|
+
if kivy_build in {'android'}:
|
|
12
|
+
return True
|
|
13
|
+
elif 'P4A_BOOTSTRAP' in os.environ:
|
|
14
|
+
return True
|
|
15
|
+
elif 'ANDROID_ARGUMENT' in os.environ:
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if is_platform_android():
|
|
22
|
+
ON_ANDROID = True
|
|
23
|
+
|
|
24
|
+
def from_service_file():
|
|
25
|
+
return 'PYTHON_SERVICE_ARGUMENT' in os.environ
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def on_flet_app():
|
|
29
|
+
return os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME")
|
|
30
|
+
|
|
31
|
+
def get_activity_class_name():
|
|
32
|
+
ACTIVITY_CLASS_NAME = os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME") # flet python
|
|
33
|
+
if not ACTIVITY_CLASS_NAME:
|
|
34
|
+
try:
|
|
35
|
+
from android import config
|
|
36
|
+
ACTIVITY_CLASS_NAME = config.JAVA_NAMESPACE
|
|
37
|
+
except (ImportError, AttributeError):
|
|
38
|
+
ACTIVITY_CLASS_NAME = 'org.kivy.android'
|
|
39
|
+
return ACTIVITY_CLASS_NAME
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_python_activity():
|
|
44
|
+
if not ON_ANDROID:
|
|
45
|
+
from .facade import PythonActivity
|
|
46
|
+
return PythonActivity
|
|
47
|
+
ACTIVITY_CLASS_NAME = get_activity_class_name()
|
|
48
|
+
if on_flet_app():
|
|
49
|
+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
|
50
|
+
else:
|
|
51
|
+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME + '.PythonActivity')
|
|
52
|
+
return PythonActivity
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_python_service():
|
|
56
|
+
if not ON_ANDROID:
|
|
57
|
+
return None
|
|
58
|
+
PythonService = autoclass(get_activity_class_name() + '.PythonService')
|
|
59
|
+
return PythonService.mService
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_python_activity_context():
|
|
63
|
+
if not ON_ANDROID:
|
|
64
|
+
from .facade import Context
|
|
65
|
+
return Context
|
|
66
|
+
|
|
67
|
+
PythonActivity = get_python_activity()
|
|
68
|
+
if from_service_file():
|
|
69
|
+
service = get_python_service()
|
|
70
|
+
context = service.getApplication().getApplicationContext()
|
|
71
|
+
else:
|
|
72
|
+
context = PythonActivity.mActivity
|
|
73
|
+
return context
|
|
74
|
+
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""For autocomplete Storing Reference to Available Methods"""
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from .aw_logging import logger
|
|
4
|
+
|
|
5
|
+
Importance = Literal['urgent', 'high', 'medium', 'low', 'none']
|
|
6
|
+
"""
|
|
7
|
+
:argument urgent - Makes a sound and appears as a heads-up notification.
|
|
8
|
+
:argument high - Makes a sound.
|
|
9
|
+
:argument urgent - Makes no sound.
|
|
10
|
+
:argument urgent - Makes no sound and doesn't appear in the status bar.
|
|
11
|
+
:argument urgent - Makes no sound and doesn't in the status bar or shade.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# For Dev
|
|
16
|
+
# Idea for typing autocompletion and reference
|
|
17
|
+
class Bundle:
|
|
18
|
+
def putString(self, key, value):
|
|
19
|
+
logger.debug(f"[MOCK] Bundle.putString called with key={key}, value={value}")
|
|
20
|
+
|
|
21
|
+
def putInt(self, key, value):
|
|
22
|
+
logger.debug(f"[MOCK] Bundle.putInt called with key={key}, value={value}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class String(str):
|
|
26
|
+
def __new__(cls, value):
|
|
27
|
+
logger.debug(f"[MOCK] String created with value={value}")
|
|
28
|
+
return str.__new__(cls, value)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Intent:
|
|
32
|
+
FLAG_ACTIVITY_NEW_TASK = 'FACADE_FLAG_ACTIVITY_NEW_TASK'
|
|
33
|
+
CATEGORY_DEFAULT = 'FACADE_FLAG_CATEGORY_DEFAULT'
|
|
34
|
+
|
|
35
|
+
def __init__(self, context='', activity=''):
|
|
36
|
+
self.obj = {}
|
|
37
|
+
logger.debug(f"[MOCK] Intent initialized with context={context}, activity={activity}")
|
|
38
|
+
|
|
39
|
+
def setAction(self, action):
|
|
40
|
+
logger.debug(f"[MOCK] Intent.setAction called with: {action}")
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def addFlags(self, *flags):
|
|
44
|
+
logger.debug(f"[MOCK] Intent.addFlags called with: {flags}")
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def setData(self, uri):
|
|
48
|
+
logger.debug(f"[MOCK] Intent.setData called with: {uri}")
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def setFlags(self, intent_flag):
|
|
52
|
+
logger.debug(f"[MOCK] Intent.setFlags called with: {intent_flag}")
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def addCategory(self, intent_category):
|
|
56
|
+
logger.debug(f"[MOCK] Intent.addCategory called with: {intent_category}")
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def getAction(self):
|
|
60
|
+
logger.debug("[MOCK] Intent.getAction called")
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def getStringExtra(self, key):
|
|
64
|
+
logger.debug(f"[MOCK] Intent.getStringExtra called with key={key}")
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def putExtra(self, key, value):
|
|
68
|
+
self.obj[key] = value
|
|
69
|
+
logger.debug(f"[MOCK] Intent.putExtra called with key={key}, value={value}")
|
|
70
|
+
|
|
71
|
+
def putExtras(self, bundle: Bundle):
|
|
72
|
+
self.obj['bundle'] = bundle
|
|
73
|
+
logger.debug(f"[MOCK] Intent.putExtras called with bundle={bundle}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PendingIntent:
|
|
77
|
+
FLAG_IMMUTABLE = ''
|
|
78
|
+
FLAG_UPDATE_CURRENT = ''
|
|
79
|
+
|
|
80
|
+
def getActivity(self, context, value, action_intent, pending_intent_type):
|
|
81
|
+
logger.debug(
|
|
82
|
+
f"[MOCK] PendingIntent.getActivity called with "
|
|
83
|
+
f"context={context}, value={value}, action_intent={action_intent}, type={pending_intent_type}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class BitmapFactory:
|
|
88
|
+
def decodeStream(self, stream):
|
|
89
|
+
logger.debug(f"[MOCK] BitmapFactory.decodeStream called with stream={stream}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class BuildVersion:
|
|
93
|
+
SDK_INT = 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Manifest:
|
|
97
|
+
POST_NOTIFICATIONS = 'FACADE_IMPORT'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Settings:
|
|
101
|
+
ACTION_APP_NOTIFICATION_SETTINGS = 'FACADE_IMPORT_ACTION_APP_NOTIFICATION_SETTINGS'
|
|
102
|
+
EXTRA_APP_PACKAGE = 'FACADE_IMPORT_EXTRA_APP_PACKAGE'
|
|
103
|
+
ACTION_APPLICATION_DETAILS_SETTINGS = 'FACADE_IMPORT_ACTION_APPLICATION_DETAILS_SETTINGS'
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Uri:
|
|
107
|
+
def __init__(self, package_name):
|
|
108
|
+
logger.debug("[MOCK] Uri initialized")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class NotificationManager:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class NotificationChannel:
|
|
116
|
+
def __init__(self, channel_id, channel_name, importance):
|
|
117
|
+
self.description = None
|
|
118
|
+
self.channel_id = channel_id
|
|
119
|
+
self.channel = None
|
|
120
|
+
logger.debug(
|
|
121
|
+
f"[MOCK] NotificationChannel initialized with "
|
|
122
|
+
f"id={channel_id}, name={channel_name}, importance={importance}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def createNotificationChannel(self, channel):
|
|
126
|
+
self.channel = channel
|
|
127
|
+
logger.debug(f"[MOCK] NotificationChannel.createNotificationChannel called with channel={channel}")
|
|
128
|
+
|
|
129
|
+
def getNotificationChannel(self, channel_id):
|
|
130
|
+
self.channel_id = channel_id
|
|
131
|
+
logger.debug(f"[MOCK] NotificationChannel.getNotificationChannel called with id={channel_id}")
|
|
132
|
+
|
|
133
|
+
def setDescription(self, description):
|
|
134
|
+
self.description = description
|
|
135
|
+
logger.debug(f"[MOCK] NotificationChannel.setDescription called with description={description}")
|
|
136
|
+
|
|
137
|
+
def getId(self):
|
|
138
|
+
logger.debug(f"[MOCK] NotificationChannel.getId called, returning {self.channel_id}")
|
|
139
|
+
return self.channel_id
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class IconCompat:
|
|
143
|
+
def createWithBitmap(self, bitmap):
|
|
144
|
+
logger.debug(f"[MOCK] IconCompat.createWithBitmap called with bitmap={bitmap}")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Color:
|
|
148
|
+
def __init__(self):
|
|
149
|
+
logger.debug("[MOCK] Color initialized")
|
|
150
|
+
|
|
151
|
+
def parseColor(self, color: str):
|
|
152
|
+
logger.debug(f"[MOCK] Color.parseColor called with color={color}")
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class RemoteViews:
|
|
157
|
+
def __init__(self, package_name, small_layout_id):
|
|
158
|
+
logger.debug(
|
|
159
|
+
f"[MOCK] RemoteViews initialized with package_name={package_name}, layout_id={small_layout_id}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def createWithBitmap(self, bitmap):
|
|
163
|
+
logger.debug(f"[MOCK] RemoteViews.createWithBitmap called with bitmap={bitmap}")
|
|
164
|
+
|
|
165
|
+
def setTextViewText(self, id, text):
|
|
166
|
+
logger.debug(f"[MOCK] RemoteViews.setTextViewText called with id={id}, text={text}")
|
|
167
|
+
|
|
168
|
+
def setTextColor(self, id, color: Color):
|
|
169
|
+
logger.debug(f"[MOCK] RemoteViews.setTextColor called with id={id}, color={color}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class NotificationManagerCompat:
|
|
173
|
+
IMPORTANCE_HIGH = 4
|
|
174
|
+
IMPORTANCE_DEFAULT = 3
|
|
175
|
+
IMPORTANCE_LOW = ''
|
|
176
|
+
IMPORTANCE_MIN = ''
|
|
177
|
+
IMPORTANCE_NONE = ''
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class NotificationCompat:
|
|
181
|
+
DEFAULT_ALL = 3
|
|
182
|
+
PRIORITY_HIGH = 4
|
|
183
|
+
PRIORITY_DEFAULT = ''
|
|
184
|
+
PRIORITY_LOW = ''
|
|
185
|
+
PRIORITY_MIN = ''
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class MActions:
|
|
189
|
+
def clear(self):
|
|
190
|
+
"""This Removes all buttons"""
|
|
191
|
+
logger.debug('[MOCK] MActions.clear called')
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class NotificationCompatBuilder:
|
|
195
|
+
def __init__(self, context, channel_id):
|
|
196
|
+
self.mActions = MActions()
|
|
197
|
+
|
|
198
|
+
def setProgress(self, max_value, current_value, endless):
|
|
199
|
+
logger.debug(f"[MOCK] setProgress called max={max_value}, current={current_value}, endless={endless}")
|
|
200
|
+
|
|
201
|
+
def setStyle(self, style):
|
|
202
|
+
logger.debug(f"[MOCK] setStyle called with style={style}")
|
|
203
|
+
|
|
204
|
+
def setContentTitle(self, title):
|
|
205
|
+
logger.debug(f"[MOCK] setContentTitle called with title={title}")
|
|
206
|
+
|
|
207
|
+
def setContentText(self, text):
|
|
208
|
+
logger.debug(f"[MOCK] setContentText called with text={text}")
|
|
209
|
+
|
|
210
|
+
def setSmallIcon(self, icon):
|
|
211
|
+
logger.debug(f"[MOCK] setSmallIcon called with icon={icon}")
|
|
212
|
+
|
|
213
|
+
def setLargeIcon(self, icon):
|
|
214
|
+
logger.debug(f"[MOCK] setLargeIcon called with icon={icon}")
|
|
215
|
+
|
|
216
|
+
def setAutoCancel(self, auto_cancel: bool):
|
|
217
|
+
logger.debug(f"[MOCK] setAutoCancel called with auto_cancel={auto_cancel}")
|
|
218
|
+
|
|
219
|
+
def setPriority(self, priority: Importance):
|
|
220
|
+
logger.debug(f"[MOCK] setPriority called with priority={priority}")
|
|
221
|
+
|
|
222
|
+
def setDefaults(self, defaults):
|
|
223
|
+
logger.debug(f"[MOCK] setDefaults called with defaults={defaults}")
|
|
224
|
+
|
|
225
|
+
def setOngoing(self, persistent: bool):
|
|
226
|
+
logger.debug(f"[MOCK] setOngoing called with persistent={persistent}")
|
|
227
|
+
|
|
228
|
+
def setOnlyAlertOnce(self, state):
|
|
229
|
+
logger.debug(f"[MOCK] setOnlyAlertOnce called with state={state}")
|
|
230
|
+
|
|
231
|
+
def build(self):
|
|
232
|
+
logger.debug("[MOCK] build called")
|
|
233
|
+
|
|
234
|
+
def setContentIntent(self, pending_action_intent: PendingIntent):
|
|
235
|
+
logger.debug(f"[MOCK] setContentIntent called with {pending_action_intent}")
|
|
236
|
+
|
|
237
|
+
def addAction(self, icon_int, action_text, pending_action_intent):
|
|
238
|
+
logger.debug(
|
|
239
|
+
f"[MOCK] addAction called with icon={icon_int}, text={action_text}, intent={pending_action_intent}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def setShowWhen(self, state):
|
|
243
|
+
logger.debug(f"[MOCK] setShowWhen called with state={state}")
|
|
244
|
+
|
|
245
|
+
def setWhen(self, time_ms):
|
|
246
|
+
logger.debug(f"[MOCK] setWhen called with time_ms={time_ms}")
|
|
247
|
+
|
|
248
|
+
def setCustomContentView(self, layout):
|
|
249
|
+
logger.debug(f"[MOCK] setCustomContentView called with layout={layout}")
|
|
250
|
+
|
|
251
|
+
def setCustomBigContentView(self, layout):
|
|
252
|
+
logger.debug(f"[MOCK] setCustomBigContentView called with layout={layout}")
|
|
253
|
+
|
|
254
|
+
def setSubText(self, text):
|
|
255
|
+
logger.debug(f"[MOCK] setSubText called with text={text}")
|
|
256
|
+
|
|
257
|
+
def setColor(self, color: Color) -> None:
|
|
258
|
+
logger.debug(f"[MOCK] setColor called with color={color}")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class NotificationCompatBigTextStyle:
|
|
262
|
+
def bigText(self, body):
|
|
263
|
+
logger.debug(f"[MOCK] NotificationCompatBigTextStyle.bigText called with body={body}")
|
|
264
|
+
return self
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class NotificationCompatBigPictureStyle:
|
|
268
|
+
def bigPicture(self, bitmap):
|
|
269
|
+
logger.debug(f"[MOCK] NotificationCompatBigPictureStyle.bigPicture called with bitmap={bitmap}")
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class NotificationCompatInboxStyle:
|
|
274
|
+
def addLine(self, line):
|
|
275
|
+
logger.debug(f"[MOCK] NotificationCompatInboxStyle.addLine called with line={line}")
|
|
276
|
+
return self
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class NotificationCompatDecoratedCustomViewStyle:
|
|
280
|
+
def __init__(self):
|
|
281
|
+
logger.debug("[MOCK] NotificationCompatDecoratedCustomViewStyle initialized")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Permission:
|
|
285
|
+
POST_NOTIFICATIONS = str
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def check_permission(permission: Permission.POST_NOTIFICATIONS):
|
|
289
|
+
logger.debug(f"[MOCK] check_permission called with {permission}")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def request_permissions(_list: [], _callback):
|
|
293
|
+
logger.debug(f"[MOCK] request_permissions called with {_list}")
|
|
294
|
+
_callback()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class AndroidActivity:
|
|
298
|
+
def bind(self, on_new_intent):
|
|
299
|
+
logger.debug(f"[MOCK] AndroidActivity.bind called with {on_new_intent}")
|
|
300
|
+
|
|
301
|
+
def unbind(self, on_new_intent):
|
|
302
|
+
logger.debug(f"[MOCK] AndroidActivity.unbind called with {on_new_intent}")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class PythonActivity:
|
|
306
|
+
def __init__(self):
|
|
307
|
+
logger.debug("[MOCK] PythonActivity initialized")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class DummyIcon:
|
|
311
|
+
icon = 101
|
|
312
|
+
|
|
313
|
+
def __init__(self):
|
|
314
|
+
logger.debug("[MOCK] DummyIcon initialized")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class Context:
|
|
318
|
+
def __init__(self):
|
|
319
|
+
logger.debug("[MOCK] Context initialized")
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def getApplicationInfo():
|
|
323
|
+
logger.debug("[MOCK] Context.getApplicationInfo called")
|
|
324
|
+
return DummyIcon
|
|
325
|
+
|
|
326
|
+
@staticmethod
|
|
327
|
+
def getResources():
|
|
328
|
+
logger.debug("[MOCK] Context.getResources called")
|
|
329
|
+
return Resources
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def getPackageName():
|
|
333
|
+
logger.debug("[MOCK] Context.getPackageName called")
|
|
334
|
+
return None # TODO get package name from buildozer.spec file
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class Resources:
|
|
338
|
+
@staticmethod
|
|
339
|
+
def getIdentifier(layout_name, folder_base, package_name):
|
|
340
|
+
logger.debug(
|
|
341
|
+
f"[MOCK] Resources.getIdentifier called "
|
|
342
|
+
f"layout={layout_name}, folder={folder_base}, package={package_name}"
|
|
343
|
+
)
|
|
344
|
+
return 0
|
|
345
|
+
|
|
346
|
+
def setTextViewText(self, text_id, text):
|
|
347
|
+
logger.debug(f"[MOCK] Resources.setTextViewText called with text_id={text_id}, text={text}")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class AppWidgetManager:
|
|
352
|
+
@classmethod #imporatnt
|
|
353
|
+
def getInstance(cls, context):
|
|
354
|
+
logger.debug(f"[MOCK] AppWidgetManager got context instance={context}")
|
|
355
|
+
return cls
|
|
356
|
+
|
|
357
|
+
@classmethod #important 1
|
|
358
|
+
def updateAppWidget(cls, widget_ids, java_view_object):
|
|
359
|
+
logger.debug(f"[MOCK] updateAppWidget called with widget_ids={widget_ids}, java_view_object={java_view_object}")
|
|
360
|
+
|
|
361
|
+
@classmethod #important 2
|
|
362
|
+
def getAppWidgetIds(cls,component):
|
|
363
|
+
logger.debug(f"[MOCK] getAppWidgetIds called with component={component}")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class ComponentName:
|
|
368
|
+
def __init__(self, context='', apk_name=''):
|
|
369
|
+
logger.debug(f"[MOCK] ComponentName initialized with context={context}, apk_name={apk_name}")
|
|
370
|
+
|
|
371
|
+
def getInstance(self,context):
|
|
372
|
+
logger.debug(f"[MOCK] AppWidgetManager got context instance={context}")
|
|
373
|
+
return self
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .config import is_platform_android
|
|
2
|
+
|
|
3
|
+
if is_platform_android():
|
|
4
|
+
from jnius import autoclass
|
|
5
|
+
String = autoclass("java.lang.String")
|
|
6
|
+
RemoteViews_ = autoclass('android.widget.RemoteViews')
|
|
7
|
+
AppWidgetManager_ = autoclass('android.appwidget.AppWidgetManager')
|
|
8
|
+
ComponentName = autoclass('android.content.ComponentName')
|
|
9
|
+
|
|
10
|
+
else:
|
|
11
|
+
raise ImportError("Not on Android, Unable to import Java classes")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
print("----------------------Running android_widget.maker-----------------")
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Receiver:
|
|
9
|
+
name: str
|
|
10
|
+
actions: List[str]
|
|
11
|
+
enabled: bool = True
|
|
12
|
+
exported: bool = False
|
|
13
|
+
label: Optional[str] = None
|
|
14
|
+
meta_name: Optional[str] = "android.appwidget.provider"
|
|
15
|
+
meta_resource: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
def to_xml(self, package: str) -> str:
|
|
18
|
+
attrs = [
|
|
19
|
+
f'android:name="{package}.{self.name}"',
|
|
20
|
+
f'android:enabled="{str(self.enabled).lower()}"',
|
|
21
|
+
f'android:exported="{str(self.exported).lower()}"',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
if self.label:
|
|
25
|
+
attrs.append(f'android:label="{self.label}"')
|
|
26
|
+
|
|
27
|
+
xml = [f"<receiver {' '.join(attrs)}>"]
|
|
28
|
+
xml.append(" <intent-filter>")
|
|
29
|
+
|
|
30
|
+
for action in self.actions:
|
|
31
|
+
xml.append(f' <action android:name="{action}" />')
|
|
32
|
+
|
|
33
|
+
xml.append(" </intent-filter>")
|
|
34
|
+
|
|
35
|
+
if self.meta_resource:
|
|
36
|
+
xml.append(
|
|
37
|
+
f' <meta-data android:name="{self.meta_name}"\n'
|
|
38
|
+
f' android:resource="{self.meta_resource}" />'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
xml.append("</receiver>")
|
|
42
|
+
return "\n".join(xml)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def generate_receivers(package: str) -> str:
|
|
46
|
+
receivers = [
|
|
47
|
+
Receiver(
|
|
48
|
+
name="Action1",
|
|
49
|
+
actions=["android.intent.action.BOOT_COMPLETED"],
|
|
50
|
+
),
|
|
51
|
+
Receiver(
|
|
52
|
+
name="SimpleWidget",
|
|
53
|
+
label="Simple Text",
|
|
54
|
+
actions=["android.appwidget.action.APPWIDGET_UPDATE"],
|
|
55
|
+
meta_resource="@xml/widgetproviderinfo",
|
|
56
|
+
),
|
|
57
|
+
Receiver(
|
|
58
|
+
name="ButtonWidget",
|
|
59
|
+
label="Counter Button Demo",
|
|
60
|
+
actions=["android.appwidget.action.APPWIDGET_UPDATE"],
|
|
61
|
+
meta_resource="@xml/button_widget_provider",
|
|
62
|
+
),
|
|
63
|
+
Receiver(
|
|
64
|
+
name="Image1",
|
|
65
|
+
actions=[
|
|
66
|
+
"android.intent.action.BOOT_COMPLETED",
|
|
67
|
+
"android.appwidget.action.APPWIDGET_UPDATE",
|
|
68
|
+
],
|
|
69
|
+
meta_resource="@xml/image_test_widget_info",
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
return "\n\n".join(r.to_xml(package) for r in receivers)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
print("------------------ Hello from android_widget.service_injector ------------------")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def inject_foreground_service_types(
|
|
81
|
+
manifest_text: str,
|
|
82
|
+
package: str,
|
|
83
|
+
services: dict[str, str],
|
|
84
|
+
) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Inject android:foregroundServiceType into <service /> tags.
|
|
87
|
+
:param manifest_text: AndroidManifest.xml file Text Content
|
|
88
|
+
:param package: package.domain + "." + package.name
|
|
89
|
+
:param services:
|
|
90
|
+
{
|
|
91
|
+
"service_name": "service_type",
|
|
92
|
+
"music": "mediaPlayback",
|
|
93
|
+
"location": "location"
|
|
94
|
+
}
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
for name, fgs_type in services.items():
|
|
98
|
+
service_name = f"{package}.Service{name.capitalize()}"
|
|
99
|
+
target = f'android:name="{service_name}"'
|
|
100
|
+
|
|
101
|
+
pos = manifest_text.find(target)
|
|
102
|
+
if pos == -1:
|
|
103
|
+
print(f"Error_101: {service_name} not found in manifest")
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
# Find the end of the <service ... /> tag
|
|
107
|
+
end = manifest_text.find("/>", pos)
|
|
108
|
+
if end == -1:
|
|
109
|
+
print(f"Error_101: {service_name} found but no '/>' closing tag")
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
segment = manifest_text[pos:end]
|
|
113
|
+
|
|
114
|
+
if "android:foregroundServiceType=" in segment:
|
|
115
|
+
print(f"Error_101: {service_name} already has foregroundServiceType")
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
manifest_text = (
|
|
119
|
+
manifest_text[:end]
|
|
120
|
+
+ f' android:foregroundServiceType="{fgs_type}"'
|
|
121
|
+
+ manifest_text[end:]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
print(
|
|
125
|
+
f"Successfully_101: Added foregroundServiceType='{fgs_type}' "
|
|
126
|
+
f"to {service_name}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return manifest_text
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SpecFile:
|
|
5
|
+
def __init__(self, path: str):
|
|
6
|
+
self.path = Path(path)
|
|
7
|
+
self.data = {}
|
|
8
|
+
self._parse()
|
|
9
|
+
|
|
10
|
+
def _parse(self):
|
|
11
|
+
current_section = None
|
|
12
|
+
|
|
13
|
+
for raw_line in self.path.read_text().splitlines():
|
|
14
|
+
line = raw_line.strip()
|
|
15
|
+
|
|
16
|
+
# skip empty lines and full-line comments
|
|
17
|
+
if not line or line.startswith("#"):
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
# section header
|
|
21
|
+
if line.startswith("[") and line.endswith("]"):
|
|
22
|
+
current_section = line[1:-1].strip()
|
|
23
|
+
self.data.setdefault(current_section, {})
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
# key = value lines
|
|
27
|
+
if "=" in line and current_section:
|
|
28
|
+
key, value = line.split("=", 1)
|
|
29
|
+
|
|
30
|
+
key = key.strip()
|
|
31
|
+
value = value.strip()
|
|
32
|
+
|
|
33
|
+
# ignore commented keys like "# key = value"
|
|
34
|
+
if key.startswith("#"):
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
self.data[current_section][key] = value
|
|
38
|
+
|
|
39
|
+
def get(self, section: str, key: str, default=None):
|
|
40
|
+
return self.data.get(section, {}).get(key, default)
|
|
41
|
+
|
|
42
|
+
def section(self, section: str) -> dict:
|
|
43
|
+
return self.data.get(section, {}).copy()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: android-widgets
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: This would be used to auto gen XMLs for Android Home Screen widgets.
|
|
5
5
|
Author-email: Fabian <fector101@yahoo.com>
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
-
Project-URL: Source, https://github.com/
|
|
10
|
-
Project-URL: Tracker, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/Fector101/kivy-androidwidgets
|
|
8
|
+
Project-URL: Documentation, https://github.com/Fector101/kivy-androidwidgets
|
|
9
|
+
Project-URL: Source, https://github.com/Fector101/kivy-androidwidgets
|
|
10
|
+
Project-URL: Tracker, https://github.com/Fector101/kivy-androidwidgets/issues
|
|
11
11
|
Keywords: kivy,android,widgets,hello-world,python-package
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -27,7 +27,7 @@ Complete Sample: [working app](https://github.com/Fector101/wallpaper-carousel)
|
|
|
27
27
|
5 Steps For a simple widget
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
30
|
+
### step 1: First you Design How you want the Widget to Look [it's Layout].
|
|
31
31
|
Store it in: `res/layout/simple_widget.xml`
|
|
32
32
|
This a simple widget with a text
|
|
33
33
|
```xml
|
|
@@ -50,7 +50,7 @@ This a simple widget with a text
|
|
|
50
50
|
|
|
51
51
|
</LinearLayout>
|
|
52
52
|
```
|
|
53
|
-
step 2: Create an xml containing the info about the widget.
|
|
53
|
+
### step 2: Create an xml containing the info about the widget.
|
|
54
54
|
Like: size, preview icon and others
|
|
55
55
|
path: `res/xml/widgetproviderinfo.xml`
|
|
56
56
|
```xml
|
|
@@ -66,7 +66,7 @@ path: `res/xml/widgetproviderinfo.xml`
|
|
|
66
66
|
```
|
|
67
67
|
Create preview image png in right path`res/drawable/ic_launcher_foreground.png`
|
|
68
68
|
|
|
69
|
-
step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
69
|
+
### step 3: Create a `AppWidgetProvider` it's used to receive events for widget.
|
|
70
70
|
path: `src/SimpleWidget.java`.
|
|
71
71
|
This will receive an event when widget is add to change it's text
|
|
72
72
|
|
|
@@ -99,7 +99,8 @@ public class SimpleWidget extends AppWidgetProvider {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
### Step 4: Automate injecting Receiver in XML
|
|
103
104
|
|
|
104
105
|
path:`p4a/hook.py`
|
|
105
106
|
|
|
@@ -138,16 +139,19 @@ def after_apk_build(toolchain: ToolchainCL):
|
|
|
138
139
|
print("Successfully_101: Manifest update completed successfully!")
|
|
139
140
|
|
|
140
141
|
```
|
|
141
|
-
|
|
142
|
+
|
|
143
|
+
### Step 5: From `buildozer.spec` tell it you want to add resources, src and p4a hook
|
|
142
144
|
```ini
|
|
143
145
|
android.add_resources = res
|
|
144
146
|
android.add_src = src
|
|
145
147
|
p4a.hook = p4a/hook.py
|
|
146
148
|
```
|
|
147
149
|
|
|
148
|
-
For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
149
|
-
---
|
|
150
150
|
|
|
151
151
|
Sample Image:
|
|
152
152
|
|
|
153
153
|

|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### For More widget customisation check: [How to Customise.md](how-to-customise.md)
|
|
157
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
android_widgets/__init__.py
|
|
4
|
+
android_widgets/aw_logging.py
|
|
5
|
+
android_widgets/config.py
|
|
6
|
+
android_widgets/facade.py
|
|
7
|
+
android_widgets/java_classes.py
|
|
8
|
+
android_widgets/maker.py
|
|
9
|
+
android_widgets/tools.py
|
|
10
|
+
android_widgets.egg-info/PKG-INFO
|
|
11
|
+
android_widgets.egg-info/SOURCES.txt
|
|
12
|
+
android_widgets.egg-info/dependency_links.txt
|
|
13
|
+
android_widgets.egg-info/requires.txt
|
|
14
|
+
android_widgets.egg-info/top_level.txt
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "android-widgets"
|
|
7
|
-
version = "0.1.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "This would be used to auto gen XMLs for Android Home Screen widgets."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Fabian", email = "fector101@yahoo.com" }
|
|
@@ -32,10 +32,10 @@ classifiers = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
|
-
Homepage = "https://github.com/
|
|
36
|
-
Documentation = "https://github.com/
|
|
37
|
-
Source = "https://github.com/
|
|
38
|
-
Tracker = "https://github.com/
|
|
35
|
+
Homepage = "https://github.com/Fector101/kivy-androidwidgets"
|
|
36
|
+
Documentation = "https://github.com/Fector101/kivy-androidwidgets"
|
|
37
|
+
Source = "https://github.com/Fector101/kivy-androidwidgets"
|
|
38
|
+
Tracker = "https://github.com/Fector101/kivy-androidwidgets/issues"
|
|
39
39
|
|
|
40
40
|
[tool.setuptools.packages.find]
|
|
41
41
|
where = ["."]
|
{android_widgets-0.1.0 → android_widgets-0.1.2}/android_widgets.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|