Python Kivy/Pyjnius android NotificationListenerService - python

I want to create an application for android with kivy in python, which Listens for notification.
I created a notification_listener.py:
from kivy import platform
if platform == "android":
from jnius import autoclass, cast, PythonJavaClass, java_method
from android.runnable import run_on_ui_thread
PythonActivity = autoclass("org.kivy.android.PythonActivity")
Activity = autoclass("android.app.Activity")
Context = autoclass("android.content.Context")
NotificationListenerService = autoclass("android.service.notification.NotificationListenerService")
StatusBarNotification = autoclass("android.service.notification.StatusBarNotification")
Log = autoclass("android.util.Log")
Toast = autoclass("android.widget.Toast")
String = autoclass("java.lang.string")
CharSequence = autoclass("java.lang.CharSequence")
activity = PythonActivity.mActivity
currentActivity = cast(Activity, activity)
context = cast(Context, currentActivity.getApplicationContext())
class NotificationListener(PythonJavaClass):
__javaclass__ = "android.service.notification.NotificationListenerService"
__javacontext__ = "app"
#java_method("()V")
def onCreate(self):
super(NotificationListener, self).onCreate()
text = cast(CharSequence, String("Listener started..."))
toast = Toast.makeText(context, text, Toast.LENGTH_LONG)
toast.show()
#java_method("(Landroid/service/notification/StatusBarNotification)V")
def onNotificationPosted(self, sbn):
notification = cast(StatusBarNotification, sbn)
extras = notification.getNotification().extras
tag = String("Notification recived")
msg_title = String("title: %s" % (extras.getString("android.title")))
msg_text = String("text: %s" % (extras.getString("android.text")))
Log.v(tag, msg_title)
Log.v(tag, msg_text)
text = cast(CharSequence, String("Notification recieved..."))
toast = Toast.makeText(context, text, Toast.LENGTH_LONG)
toast.show()
but how do I have to add the to the AndroidManifest.xml?
if I would do it in Java, the following code would be correct but its a python file, so how do I have to implement it? 🤔
<service name=".NotificationListener"
android:label="notification_listener"
android:permissions="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>

Recently I made it work, so I am writing here about my experience.
Buildozer does not provide any options for registering a service with permissions and an intent-filter. However, you can customize AndroidManifest.xml from the template file which is located in your .buildozer directory.
/your/project/home/.buildozer/android/platform/build-armeabi-v7a/dists/yourapp__armeabi-v7a/templates/AndroidManifest.tmpl.xml
You can insert the xml block between "end-if" and "if" so that it won't be changed by the buildozer.spec settings.
Next thing you need to make is a java class file. Unfortunately, python class cannot be bound to NotificationListeners, so you need to manually copy a java class that receives StatusBarNotifications.
The path is going to be:
/your/project/home/.buildozer/android/platform/build-armeabi-v7a/dists/yourapp__armeabi-v7a/src/main/java/your/app/domain/NotificationListener.java
With the java class, you can broadcast the StatusBarNotification to the python side,getApplicationContext().sendBroadcast(intent);, and p4a's android.broadcast.BroadcastReceiver will receive the object on main.py.
Java class
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
try {
Bundle bundle = new Bundle();
bundle.putParcelable("sbn",sbn);
Intent intent = new Intent("action_name_matches_with_java_side");
intent.putExtra("sbn",sbn);
getApplicationContext().sendBroadcast(intent);
} catch(Exception e) {
Log.v("KIVYAPP","Notification broadcasting prohibited");
}
}
main.py
class YourApp(App):
def build(self):
...
self.br = BroadcastReceiver(self.onReceive,actions=["action_name_matches_with_java_side"])
self.br.start()
...
def onReceive(self, context, intent):
bundle = cast(Bundle, intent.getExtras())
sbn = cast(StatusBarNotification,bundle.getParcelable("sbn"))
...

Related

How to combine apispec and flask-swagger-ui to make a Swagger page?

I can't find anything on the internet about how to actually extract and utilize the OpenAPI specification generated by the apispec package to make a Swagger page.
I don't want to rely on a package that hasn't been being actively maintained like flask-apispec. I want to use flask-swagger-ui, apispec, and other standard/well-maintained packages only.
Here is my test app. I don't know if the APISpec is working right because the documentation doesn't tell you anything about what you do with the object, but the Flask app is functional.
from flask import Flask, request, abort
from marshmallow import Schema, fields
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
app = Flask(__name__)
spec = APISpec(
title="Doubler",
version="1.0.0",
openapi_version="3.0.2",
plugins=[FlaskPlugin(), MarshmallowPlugin()],
)
class InputSchema(Schema):
useless_1 = fields.String(required=True, description='A string')
useless_2 = fields.Int(missing=5, description='An integer')
class OutputSchema(Schema):
doublyuseless_1 = fields.String(required=True)
doublyuseless_2 = fields.Int(required=True)
inputschema = InputSchema()
outputschema = OutputSchema()
#app.route('/double', methods=['GET'])
def double():
"""A doubler.
---
get:
description: Double things
parameters:
schema:
InputSchema
responses:
200:
description: Double things
content:
application/json:
schema: OutputSchema"""
errors = inputschema.validate(request.args)
if errors:
abort(400, str(errors))
return_dict = {}
args = inputschema.load(request.args)
return_dict['doublyuseless_1'] = args['useless_1']*2
return_dict['doublyuseless_2'] = args['useless_2']*2
return outputschema.dump(return_dict)
with app.test_request_context():
spec.path(view=double)
UPDATE: with the following code, I now get a blank page at root with the Swagger title, but no content.
with app.test_request_context():
spec.path(view=double)
with open('swagger.json', 'w') as f:
dict_ = yaml.load(StringIO(spec.to_yaml()), Loader=yaml.SafeLoader)
print(dict_)
with open('swagger.json', 'w') as f:
json.dump(dict_, f)
SWAGGER_URL = '/'
API_URL = 'swagger.json'
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL, # Swagger UI static files will be mapped to '{SWAGGER_URL}/dist/'
API_URL,
config={ # Swagger UI config overrides
'app_name': "Doubler"
},
# oauth_config={ # OAuth config. See https://github.com/swagger-api/swagger-ui#oauth2-configuration .
# 'clientId': "your-client-id",
# 'clientSecret': "your-client-secret-if-required",
# 'realm': "your-realms",
# 'appName': "your-app-name",
# 'scopeSeparator': " ",
# 'additionalQueryStringParams': {'test': "hello"}
# }
)
The solution to my original problem was quite straightforward: ApiSpec has both a to_dict and a to_yaml method for exporting swagger.json. My second problem was more esoteric. I needed to use a SWAGGER_URL that was not /, because for some reason this caused the page to look for the core Swagger files at URLs like http://swagger-ui.js, which obviously didn't work. Once I changed my path to /doc, I still had a white screen, but that could be fixed by hosting the files myself at /doc (which I think flask_swagger_ui was supposed to do automatically, but hey, it worked).

add xsi:type in SOAP python spyne xml response

I am using example/complextype.py script provided in spyne official repo and I got the following
reponse:
--------------- RESPONSE ------------------------
<xml version='1.0' encoding='UTF-8'?>
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="spyne.examples.complex">
<soap11env:Body>
<tns:super_userResponse>
<ns0:userid xmlns:ns0="user">0</ns0:userid>
<ns0:username xmlns:ns0="user">root</ns0:username>
<ns0:firstname xmlns:ns0="user">Super</ns0:firstname>
<ns0:lastname xmlns:ns0="user">User</ns0:lastname>
</tns:super_userResponse>
</soap11env:Body>
</soap11env:Envelope>
what I am trying to unnderstand is how to add xsi:type attributes
<ns0:userid xmlns:ns0="user" xsi:type="integer" >0</ns0:userid>
class Permission(ComplexModel):
__namespace__ = "permission"
app = String(values=['library', 'delivery', 'accounting'])
perms = String(min_occurs=1, max_occurs=2, values=['read', 'write'])
class User(ComplexModel):
__namespace__ = "user"
userid = Integer
username = String
firstname = String
lastname = String
user_database[0] = User(
userid=0,
username='root',
firstname='Super',
lastname='User',
permissions=all_permissions
)
class UserManager(Service):
#rpc(_returns=User , _body_style='bare' )
def super_user(ctx):
return user_database[0]
if __name__ == '__main__':
from wsgiref.simple_server import make_server
application = Application([UserManager], 'spyne.examples.complex',
in_protocol=Soap11(), out_protocol=Soap11())
server = make_server('127.0.0.1', 8000, WsgiApplication(application))
server.serve_forever()
using the provided example, how can I fix it?
Spyne adds xsi:type only in case of ambiguity, which only happens in case of polymorphic types.
Eg. if the type in WSDL says Vehicle but you return a child class named Car AND you have polymorphism enabled in in the output protocol, you will get xsi:type="nsprefix:Car" opening tag of the Car instance.
If you want to override this behavior, you need to override to_parent function in a custom protocol and pass it as the output protocol to the Application instantiation.
PS: xsi:type tag is added when add_type == True here

Python, returning data collected in npyscreen

So I'm creating a basic TUI for a script I created. The goal is to collect several variables that include paths to directories and files. What I'm not sure about is once I've created the visual aspect of it, how to get those pieces of information to interact with other parts of the code.
Below is what I have so far in terms of the visual portion (the other part is about 500 lines), and honestly I'm at a loss on how to even print any of the variables set under the class and any pointers would be greatly appreciated.
#!/usr/bin/env python
# encoding: utf-8
import npyscreen
class PlistCreator(npyscreen.NPSApp):
def main(self):
screen = npyscreen.Form(name="Welcome to Nicks Playlist Creator!")
plistName = screen.add(npyscreen.TitleText, name="Playlist Name:" )
csvPath = screen.add(npyscreen.TitleFilenameCombo, name="CSV Schedule:")
toaPath = screen.add(npyscreen.TitleFilenameCombo, name="Path to ToA Video Folder:", use_two_lines=True)
outputPath = screen.add(npyscreen.TitleFilenameCombo, name = "Output Folder:", use_two_lines=True)
dateOfAir = screen.add(npyscreen.TitleDateCombo, name="Date of Air:")
timeOfStart = screen.add(npyscreen.TitleText, name="Time of Air (TC):")
screen.edit()
if __name__ == "__main__":
App = PlistCreator()
App.run()
You can get the value of any form object using the dit notation.
plistName = screen.add(npyscreen.TitleText, name="Playlist Name:" )
playlist_name = self.screen.plistName.value
etc. Note there are no parentheses after value. Once you have it in a variable, you can build another method in the class to handle the information.

Opening Eclipse editor from python with Py4J

I'm trying open a file in Eclipse editor from my python program.
Here is example how to do this with Java:
import java.io.File;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
File fileToOpen = new File("externalfile.xml");
if (fileToOpen.exists() && fileToOpen.isFile()) {
IFileStore fileStore = EFS.getLocalFileSystem().getStore(fileToOpen.toURI());
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditorOnFileStore( page, fileStore );
} catch ( PartInitException e ) {
//Put your exception handler here if you wish to
}
} else {
//Do something if the file does not exist
}
I'm trying to use this API from python with use of Py4j.
from py4j.java_gateway import JavaGateway, java_import
gateway = JavaGateway()
jvm = gateway.jvm
java_import(jvm, 'org.eclipse.core.filesystem.EFS')
java_import(jvm, 'org.eclipse.ui.PlatformUI')
fileToOpen = jvm.java.io.File('c:/test.txt')
fileStore = jvm.org.eclipse.core.filesystem.EFS.getLocalFileSystem().getStore(fileToOpen.toURI());
This works fine. But I have stacked with getting page.
page = jvm.org.eclipse.ui.PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
I'm getting None here. Looks like Rudolf Widmann have posted answer for this question here. So the java solution will be:
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
IWorkbenchWindow iw = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
}
});
But how can I do this from python? How to implement it with Py4j?
Did you try to implement the Runnable interface in Python?
class PythonRunner(object):
def __init__(self, gateway):
self.gateway = gateway
self.iw
def run(self):
self.iw = gateway.jvm.org.eclipse.ui.PlatformUI.getWorkbench().getActiveWorkbenchWindow()
class Java:
implements = ['java.lang.Runnable']
gateway = JavaGateway(start_callback_server=True)
runner = PythonRunner(gateway)
gateway.jvm.org.eclipse.swt.widgets.Display.getDefault().asyncExec(runner)
# Replace with a synchronization primitive
time.sleep(2)
page = runner.iw.getActivePage()

How do I search for unpublished Plone content in an IPython debug shell?

I like to use IPython's zope profile to inspect my Plone instance, but a few annoying permissions differences come up compared to inserting a breakpoint and hitting it with the admin user.
For example, I would like to iterate over the content objects in an unpublished testing folder. This query will return no results in the shell, but works from a breakpoint.
$ bin/instance shell
$ ipython --profile=zope
from Products.CMFPlone.utils import getToolByName
catalog = getToolByName(context, 'portal_catalog')
catalog({'path':'Plone/testing'})
Can I authenticate as admin or otherwise rejigger the permissions to fully manipulate my site from ipython?
here's the (very dirty) code I use to manage my plone app from the debug shell. It may requires some updates depending on your versions of Zope and Plone.
from sys import stdin, stdout, exit
import base64
from thread import get_ident
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
from ZPublisher.BaseRequest import RequestContainer
from ZPublisher import Publish
from AccessControl import ClassSecurityInfo, getSecurityManager
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.User import UnrestrictedUser
def loginAsUnrestrictedUser():
"""Exemple of use :
old_user = loginAsUnrestrictedUser()
# Manager stuff
loginAsUser(old_user)
"""
current_user = getSecurityManager().getUser()
newSecurityManager(None, UnrestrictedUser('manager', '', ['Manager'], []))
return current_user
def loginAsUser(user):
newSecurityManager(None, user)
def makerequest(app, stdout=stdout, query_string=None, user_pass=None):
"""Make a request suitable for CMF sites & Plone
- user_pass = "user:pass"
"""
# copy from Testing.makerequest
resp = HTTPResponse(stdout=stdout)
env = {}
env['SERVER_NAME'] = 'lxtools.makerequest.fr'
env['SERVER_PORT'] = '80'
env['REQUEST_METHOD'] = 'GET'
env['REMOTE_HOST'] = 'a.distant.host'
env['REMOTE_ADDR'] = '77.77.77.77'
env['HTTP_HOST'] = '127.0.0.1'
env['HTTP_USER_AGENT'] = 'LxToolsUserAgent/1.0'
env['HTTP_ACCEPT']='image/gif, image/x-xbitmap, image/jpeg, */* '
if user_pass:
env['HTTP_AUTHORIZATION']="Basic %s" % base64.encodestring(user_pass)
if query_string:
p_q = query_string.split('?')
if len(p_q) == 1:
env['PATH_INFO'] = p_q[0]
elif len(p_q) == 2:
(env['PATH_INFO'], env['QUERY_STRING'])=p_q
else:
raise TypeError, ''
req = HTTPRequest(stdin, env, resp)
req['URL1']=req['URL'] # fix for CMFQuickInstaller
#
# copy/hacked from Localizer __init__ patches
# first put the needed values in the request
req['HTTP_ACCEPT_CHARSET'] = 'latin-9'
#req.other['AcceptCharset'] = AcceptCharset(req['HTTP_ACCEPT_CHARSET'])
#
req['HTTP_ACCEPT_LANGUAGE'] = 'fr'
#accept_language = AcceptLanguage(req['HTTP_ACCEPT_LANGUAGE'])
#req.other['AcceptLanguage'] = accept_language
# XXX For backwards compatibility
#req.other['USER_PREF_LANGUAGES'] = accept_language
#req.other['AcceptLanguage'] = accept_language
#
# Plone stuff
#req['plone_skin'] = 'Plone Default'
#
# then store the request in Publish._requests
# with the thread id
id = get_ident()
if hasattr(Publish, '_requests'):
# we do not have _requests inside ZopeTestCase
Publish._requests[id] = req
# add a brainless session container
req['SESSION'] = {}
#
# ok, let's wrap
return app.__of__(RequestContainer(REQUEST = req))
def debug_init(app):
loginAsUnrestrictedUser()
app = makerequest(app)
return app
This lives in a wshelpers Zope product. Once the debug shell launched, it's just a matter of;
>> from Products.wshelpers import wsdebug
>> app = wsdebug.debug_init(app)
>> # now you're logged in as admin
Just use catalog.search({'path':'Plone/testing'}). It performs the same query as catalog() but does not filter the results based on the current user's permissions.
IPython's zope profile does provide a method utils.su('username') to change the current user, but it does not recognize the admin user (defined in /acl_users instead of /Plone/acl_users) and after calling it subsequent calls to catalog() fail with AttributeError: 'module' object has no attribute 'checkPermission'.

Categories