From 9ed4cde1ad98da9211955acefd97b83c44e982f8 Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Tue, 31 Mar 2020 09:49:58 -0700 Subject: [PATCH 1/7] Slight refactor and cleanup, and consolidating Android-specific code. --- .gitignore | 2 ++ Dockerfile | 3 ++- README.md | 4 ++-- src/main.py | 46 +++++++++++++++++++++++----------------------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 7383d54d..b0266572 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ dist/ *.pyc build_docker +build/ bin/ +build.log diff --git a/Dockerfile b/Dockerfile index 6988e624..b84f0e3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN dpkg --add-architecture i386 && \ zlib1g-dev \ zlib1g:i386 \ python-wxgtk3.0 \ + libgtk-3-dev \ && apt-get clean # install python dependencies @@ -48,7 +49,7 @@ WORKDIR /home/kivy COPY --chown=kivy:kivy whitelist.txt project_info.template ./ COPY --chown=kivy:kivy scripts/create_dummy_project_info.py scripts/ -# Makes a dummy project_info, pretty mutch just ot get pew init to run +# Makes a dummy project_info, pretty mutch just to get pew init to run # Downlads p4a and all python dependencies for packaging in android RUN python ./scripts/create_dummy_project_info.py && pew init android diff --git a/README.md b/README.md index 2f623d8b..416b5924 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ Run `adb logcat -v brief python:D *:F` to get all debug logs from the Kolibri se - If your device doesn't aggressively kill the server, you can open Chrome and use remote debugging tools to see the logs on your desktop. - You can also leave the app open and port forward the Android device's Kolibri port using [adb](https://developer.android.com/studio/command-line/adb#forwardports): ``` - adb forward tcp:5000 tcp:5001 + adb forward tcp:8080 tcp:8081 ``` - then going into your desktop's browser and accessing `localhost:5001`. Note that you can map to any port on the host machine, the second argument. + then going into your desktop's browser and accessing `localhost:8081`. Note that you can map to any port on the host machine, the second argument. Alternatively, you can debug the webview directly. Modern Android versions should let you do so from the developer settings. diff --git a/src/main.py b/src/main.py index 5354f2f8..6d0abbec 100644 --- a/src/main.py +++ b/src/main.py @@ -13,29 +13,32 @@ pew.set_app_name("Kolibri") logging.info("Entering main.py...") +PORT = 8080 + +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(script_dir) +sys.path.append(os.path.join(script_dir, "kolibri", "dist")) + +os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri.deployment.default.settings.base" if pew.ui.platform == "android": + from jnius import autoclass PythonActivity = autoclass("org.kivy.android.PythonActivity") File = autoclass("java.io.File") Timezone = autoclass("java.util.TimeZone") + # TODO: check for storage availability, allow user to chose sd card or internal + def get_home_folder(): + kolibri_home_file = PythonActivity.getExternalFilesDir(None) + return kolibri_home_file.toString() -# TODO check for storage availibility, allow user to chose sd card or internal -def get_home_folder(): - kolibri_home_file = PythonActivity.getExternalFilesDir(None) - return kolibri_home_file.toString() - - -script_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(script_dir) -sys.path.append(os.path.join(script_dir, "kolibri", "dist")) + os.environ["KOLIBRI_HOME"] = get_home_folder() -os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri.deployment.default.settings.base" + # TODO: before shipping the app, make this contingent on debug vs production mode + os.environ["KOLIBRI_RUN_MODE"] = "android-dev" -if pew.ui.platform == "android": - os.environ["KOLIBRI_HOME"] = get_home_folder() os.environ["TZ"] = Timezone.getDefault().getDisplayName() os.environ["LC_ALL"] = "en_US.UTF-8" @@ -45,18 +48,14 @@ def get_home_folder(): def start_django(): - # remove this after Kolibri no longer needs it - if sys.version[0] == "2": - reload(sys) - sys.setdefaultencoding("utf8") - logging.info("Starting server...") from kolibri.utils.cli import main - main(["start", "--foreground", "--port=5000"]) + main(["start", "--foreground", "--port={port}".format(port=PORT)]) class Application(pew.ui.PEWApp): + def setUp(self): """ Start your UI and app run loop here. @@ -105,9 +104,9 @@ def page_loaded(self, url): def wait_for_server(self): from kolibri.utils import server - home_url = "http://localhost:5000" + home_url = "http://localhost:{port}".format(port=PORT) - # test url to see if servr has started + # test url to see if server has started def running(): try: urllib2.urlopen(home_url) @@ -127,10 +126,11 @@ def running(): logging.debug("Persisted View State: {}".format(self.view.get_view_state())) if "URL" in saved_state and saved_state["URL"].startswith(home_url): - pew.ui.run_on_main_thread(self.view.load_url(saved_state["URL"])) - return + start_url = saved_state["URL"] + else: + start_url = home_url - pew.ui.run_on_main_thread(self.view.load_url(home_url)) + pew.ui.run_on_main_thread(self.view.load_url, start_url) def get_main_window(self): return self.view From 08d865c2af4f868e47823d2f9ab0ef1d11906936 Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Thu, 2 Apr 2020 23:19:43 -0700 Subject: [PATCH 2/7] Switch Kolibri server start to a separate persistent foreground service --- Makefile | 17 +++++++--- project_info.template | 2 +- src/config.py | 1 + src/main.py | 62 +++++++++++++++++----------------- src/server.py | 77 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 src/config.py create mode 100644 src/server.py diff --git a/Makefile b/Makefile index e2930041..7510b1d7 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,11 @@ VPATH = ./dist/android/ clean: - rm -rf dist/android/*.apk project_info.json ./src/kolibri -# Replace the default loading page, so that it will be replaced with our own version -replaceloadingpage: - rm -f .buildozer/android/platform/build/dists/kolibri/webview_includes/_load.html - cp ./assets/_load.html .buildozer/android/platform/build/dists/kolibri/webview_includes/ - cp ./assets/loading-spinner.gif .buildozer/android/platform/build/dists/kolibri/webview_includes/ +deepclean: clean + rm -r ~/.local/share/python-for-android + rm -r build + yes y | docker system prune -a + rm build_docker 2> /dev/null # Extract the whl file src/kolibri: @@ -39,3 +39,10 @@ build_docker: project_info.template Dockerfile # TODO Would be better to just specify the file here? run_docker: clean project_info.json build_docker ./scripts/rundocker.sh + +launch: Kolibri%.apk + adb uninstall org.learningequality.Kolibri || true 2> /dev/null + rm dist/android/Kolibri-0-debug.apk || true 2> /dev/null + adb install dist/android/*-debug.apk + adb shell am start -n org.learningequality.Kolibri/org.kivy.android.PythonActivity + adb logcat | grep -E "python|Python| server " \ No newline at end of file diff --git a/project_info.template b/project_info.template index 523c5756..3df04fb6 100644 --- a/project_info.template +++ b/project_info.template @@ -3,7 +3,7 @@ "version": "${apk_version}", "build_number": "${build_number}", "identifier": "org.learningequality.Kolibri", - "requirements": {"android": ["python2","pyjnius","genericndkbuild", "sqlite3", "cryptography", "pyopenssl", "openssl", "six"]}, + "requirements": {"android": ["python2", "pyjnius", "genericndkbuild", "sqlite3", "cryptography", "pyopenssl", "openssl", "six"]}, "whitelist_file": {"android": "whitelist.txt"}, "icons": {"android": "icon.png"}, "launch_images": {"android": "assets/launch-image.png"}, diff --git a/src/config.py b/src/config.py new file mode 100644 index 00000000..d3e4ac46 --- /dev/null +++ b/src/config.py @@ -0,0 +1 @@ +PORT = 8080 \ No newline at end of file diff --git a/src/main.py b/src/main.py index 6d0abbec..252be6c9 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,6 @@ +import json import logging import os -import sys import time import urllib2 @@ -10,48 +10,49 @@ import pew import pew.ui +from config import PORT + pew.set_app_name("Kolibri") logging.info("Entering main.py...") -PORT = 8080 - -script_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(script_dir) -sys.path.append(os.path.join(script_dir, "kolibri", "dist")) - -os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri.deployment.default.settings.base" -if pew.ui.platform == "android": +def start_kolibri_server(port): - from jnius import autoclass + if pew.ui.platform == "android": - PythonActivity = autoclass("org.kivy.android.PythonActivity") - File = autoclass("java.io.File") - Timezone = autoclass("java.util.TimeZone") + from jnius import autoclass - # TODO: check for storage availability, allow user to chose sd card or internal - def get_home_folder(): - kolibri_home_file = PythonActivity.getExternalFilesDir(None) - return kolibri_home_file.toString() + PythonActivity = autoclass("org.kivy.android.PythonActivity") + service = autoclass('org.learningequality.Kolibri.ServiceServer') - os.environ["KOLIBRI_HOME"] = get_home_folder() + # TODO: check for storage availability, allow user to chose sd card or internal + def get_home_folder(): + kolibri_home_file = PythonActivity.mActivity.getExternalFilesDir(None) + return kolibri_home_file.toString() - # TODO: before shipping the app, make this contingent on debug vs production mode - os.environ["KOLIBRI_RUN_MODE"] = "android-dev" + # store the version name into an envvar to be picked up by Kolibri + os.environ["KOLIBRI_APK_VERSION_NAME"] = ( + PythonActivity.getPackageManager() + .getPackageInfo(PythonActivity.getPackageName(), 0) + .versionName + ) - os.environ["TZ"] = Timezone.getDefault().getDisplayName() - os.environ["LC_ALL"] = "en_US.UTF-8" + logging.info("Starting kolibri server via Android service...") - logging.info("Home folder: {}".format(os.environ["KOLIBRI_HOME"])) - logging.info("Timezone: {}".format(os.environ["TZ"])) + service.start(PythonActivity.mActivity, json.dumps({ + "HOME": get_home_folder(), + "PORT": port, + })) + else: -def start_django(): + from server import start_django - logging.info("Starting server...") - from kolibri.utils.cli import main + logging.info("Starting kolibri server directly as thread...") - main(["start", "--foreground", "--port={port}".format(port=PORT)]) + thread = pew.ui.PEWThread(target=start_django, args=(port,)) + thread.daemon = True + thread.start() class Application(pew.ui.PEWApp): @@ -68,9 +69,7 @@ def setUp(self): self.view = pew.ui.WebUIView("Kolibri", self.loader_url, delegate=self) # start thread - self.thread = pew.ui.PEWThread(target=start_django) - self.thread.daemon = True - self.thread.start() + start_kolibri_server(PORT) self.load_thread = pew.ui.PEWThread(target=self.wait_for_server) self.load_thread.daemon = True @@ -102,7 +101,6 @@ def page_loaded(self, url): self.view.webview.webview.clearHistory() def wait_for_server(self): - from kolibri.utils import server home_url = "http://localhost:{port}".format(port=PORT) diff --git a/src/server.py b/src/server.py new file mode 100644 index 00000000..f417d036 --- /dev/null +++ b/src/server.py @@ -0,0 +1,77 @@ +import json +import logging +import os +import sys + +# initialize logging before loading any third-party modules, as they may cause logging to get configured. +logging.basicConfig(level=logging.DEBUG) + +import pew +import pew.ui + +logging.info("Entering server.py...") + +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(script_dir) +sys.path.append(os.path.join(script_dir, "kolibri", "dist")) + +os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri.deployment.default.settings.base" + +# TODO: before shipping the app, make this contingent on debug vs production mode +os.environ["KOLIBRI_RUN_MODE"] = "pew-dev" + + +def start_django(port): + + from kolibri.utils.cli import main + + logging.info("Starting server...") + + main(["start", "--foreground", "--port={port}".format(port=port)]) + + +if pew.ui.platform == "android": + + from jnius import autoclass + + service_args = json.loads(os.environ.get("PYTHON_SERVICE_ARGUMENT") or "{}") + + service = autoclass('org.kivy.android.PythonService').mService + File = autoclass("java.io.File") + Timezone = autoclass("java.util.TimeZone") + AndroidString = autoclass('java.lang.String') + Drawable = autoclass("{}.R$drawable".format(service.getPackageName())) + Context = autoclass('android.content.Context') + Intent = autoclass('android.content.Intent') + PendingIntent = autoclass('android.app.PendingIntent') + NotificationBuilder = autoclass('android.app.Notification$Builder') + Notification = autoclass('android.app.Notification') + + def make_service_foreground(title, message): + PythonActivity = autoclass('org.kivy.android.PythonActivity') + app_context = service.getApplication().getApplicationContext() + notification_builder = NotificationBuilder(app_context) + notification_builder.setContentTitle(AndroidString(title)) + notification_builder.setContentText(AndroidString(message)) + notification_intent = Intent(app_context, PythonActivity) + notification_intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) + notification_intent.setAction(Intent.ACTION_MAIN) + notification_intent.addCategory(Intent.CATEGORY_LAUNCHER) + intent = PendingIntent.getActivity(service, 0, notification_intent, 0) + notification_builder.setContentIntent(intent) + notification_builder.setSmallIcon(Drawable.icon) + notification_builder.setAutoCancel(True) + new_notification = notification_builder.getNotification() + service.startForeground(1, new_notification) + + os.environ["KOLIBRI_HOME"] = service_args["HOME"] + + os.environ["TZ"] = Timezone.getDefault().getDisplayName() + os.environ["LC_ALL"] = "en_US.UTF-8" + + logging.info("Home folder: {}".format(os.environ["KOLIBRI_HOME"])) + logging.info("Timezone: {}".format(os.environ["TZ"])) + + make_service_foreground("Kolibri is running...", "Click here to resume.") + + start_django(service_args["PORT"]) From ec341bc7a4e658a5aa39587fa3694bd5359cb8a5 Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Fri, 3 Apr 2020 14:15:44 -0700 Subject: [PATCH 3/7] Pass version name in via service arguments --- src/main.py | 8 +------- src/server.py | 4 +++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main.py b/src/main.py index 252be6c9..ef341a1c 100644 --- a/src/main.py +++ b/src/main.py @@ -30,18 +30,12 @@ def get_home_folder(): kolibri_home_file = PythonActivity.mActivity.getExternalFilesDir(None) return kolibri_home_file.toString() - # store the version name into an envvar to be picked up by Kolibri - os.environ["KOLIBRI_APK_VERSION_NAME"] = ( - PythonActivity.getPackageManager() - .getPackageInfo(PythonActivity.getPackageName(), 0) - .versionName - ) - logging.info("Starting kolibri server via Android service...") service.start(PythonActivity.mActivity, json.dumps({ "HOME": get_home_folder(), "PORT": port, + "VERSION": PythonActivity.getPackageManager().getPackageInfo(PythonActivity.getPackageName(), 0).versionName, })) else: diff --git a/src/server.py b/src/server.py index f417d036..83ffde12 100644 --- a/src/server.py +++ b/src/server.py @@ -20,7 +20,6 @@ # TODO: before shipping the app, make this contingent on debug vs production mode os.environ["KOLIBRI_RUN_MODE"] = "pew-dev" - def start_django(port): from kolibri.utils.cli import main @@ -66,6 +65,9 @@ def make_service_foreground(title, message): os.environ["KOLIBRI_HOME"] = service_args["HOME"] + # store the version name into an envvar to be picked up by Kolibri + os.environ["KOLIBRI_APK_VERSION_NAME"] = service_args["VERSION"] + os.environ["TZ"] = Timezone.getDefault().getDisplayName() os.environ["LC_ALL"] = "en_US.UTF-8" From 6b713259ef5b7432db7e53c70ab8c2cb6d32d2ab Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Fri, 3 Apr 2020 14:16:31 -0700 Subject: [PATCH 4/7] Add extra_build_options for configuring service --- project_info.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project_info.template b/project_info.template index 3df04fb6..509c30c6 100644 --- a/project_info.template +++ b/project_info.template @@ -7,5 +7,6 @@ "whitelist_file": {"android": "whitelist.txt"}, "icons": {"android": "icon.png"}, "launch_images": {"android": "assets/launch-image.png"}, - "asset_dirs": ["assets"] + "asset_dirs": ["assets"], + "extra_build_options": {"android": {"services": ["server:server.py"], "extra_permissions": []}} } From 96290bdd2823553e0ff13bac89d4e44bb5180134 Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Fri, 3 Apr 2020 22:09:18 -0700 Subject: [PATCH 5/7] Add custom settings file and have it never expire the session --- src/kolibri_app_settings.py | 11 +++++++++++ src/server.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/kolibri_app_settings.py diff --git a/src/kolibri_app_settings.py b/src/kolibri_app_settings.py new file mode 100644 index 00000000..745979b7 --- /dev/null +++ b/src/kolibri_app_settings.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import os +import tempfile + +from kolibri.deployment.default.settings.base import * + +SESSION_EXPIRE_AT_BROWSER_CLOSE = False +SESSION_COOKIE_AGE = 52560000 diff --git a/src/server.py b/src/server.py index 83ffde12..54cbb575 100644 --- a/src/server.py +++ b/src/server.py @@ -15,7 +15,7 @@ sys.path.append(script_dir) sys.path.append(os.path.join(script_dir, "kolibri", "dist")) -os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri.deployment.default.settings.base" +os.environ["DJANGO_SETTINGS_MODULE"] = "kolibri_app_settings" # TODO: before shipping the app, make this contingent on debug vs production mode os.environ["KOLIBRI_RUN_MODE"] = "pew-dev" From ffde26a30277c12cae71c9515595097c4cbf8dfa Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Fri, 3 Apr 2020 22:31:22 -0700 Subject: [PATCH 6/7] Copy pre-migrated DB into Kolibri data directory to speed up startup --- .gitignore | 1 + Makefile | 5 +++++ src/db.sqlite3.empty | Bin 0 -> 921600 bytes src/server.py | 9 +++++++++ 4 files changed, 15 insertions(+) create mode 100644 src/db.sqlite3.empty diff --git a/.gitignore b/.gitignore index b0266572..ea00e90a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ build_docker build/ bin/ build.log +tmphome/ \ No newline at end of file diff --git a/Makefile b/Makefile index 441e216f..f5affe6d 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,11 @@ kolibri.apk: p4a_android_distro build_docker: Dockerfile docker build -t android_kolibri . +create_premigrated_db: + pip install whl/*.whl + KOLIBRI_HOME=tmphome kolibri manage migrate + cp tmphome/db.sqlite3 src/db.sqlite3.empty + # Run the docker image. # TODO Would be better to just specify the file here? run_docker: build_docker diff --git a/src/db.sqlite3.empty b/src/db.sqlite3.empty new file mode 100644 index 0000000000000000000000000000000000000000..5dfcc731d3ed6e9fc72610b032c52138aa12819d GIT binary patch literal 921600 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 literal 0 HcmV?d00001 diff --git a/src/server.py b/src/server.py index 54cbb575..e32814f8 100644 --- a/src/server.py +++ b/src/server.py @@ -1,6 +1,7 @@ import json import logging import os +import shutil import sys # initialize logging before loading any third-party modules, as they may cause logging to get configured. @@ -65,6 +66,14 @@ def make_service_foreground(title, message): os.environ["KOLIBRI_HOME"] = service_args["HOME"] + # copy an empty pre-migrated database into the Kolibri data directory to speed up startup + DB_TEMPLATE_PATH = "db.sqlite3.empty" + DB_PATH = os.path.join(os.environ["KOLIBRI_HOME"], "db.sqlite3") + if not os.path.exists(DB_PATH) and os.path.exists(DB_TEMPLATE_PATH): + if not os.path.exists(os.environ["KOLIBRI_HOME"]): + os.makedirs(os.environ["KOLIBRI_HOME"]) + shutil.copyfile(DB_TEMPLATE_PATH, DB_PATH) + # store the version name into an envvar to be picked up by Kolibri os.environ["KOLIBRI_APK_VERSION_NAME"] = service_args["VERSION"] From 006b56f121522a812fa8f554a40275cc0c1a1f85 Mon Sep 17 00:00:00 2001 From: Jamie Alexandre Date: Sat, 4 Apr 2020 20:34:23 -0700 Subject: [PATCH 7/7] Add SDK version to project info --- .gitignore | 4 +++- Makefile | 2 +- project_info.template | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ea00e90a..a778c3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,11 @@ project_info.json # pew output folder dist/ +__pycache__ *.pyc build_docker build/ bin/ build.log -tmphome/ \ No newline at end of file +tmphome/ +static/ \ No newline at end of file diff --git a/Makefile b/Makefile index f5affe6d..8e38bbdf 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ deepclean: clean rm build_docker 2> /dev/null # Extract the whl file -src/kolibri: +src/kolibri: clean unzip -qo "whl/kolibri*.whl" "kolibri/*" -x "kolibri/dist/cext*" -d src/ # Generate the project info file diff --git a/project_info.template b/project_info.template index 509c30c6..ad2d83e8 100644 --- a/project_info.template +++ b/project_info.template @@ -8,5 +8,5 @@ "icons": {"android": "icon.png"}, "launch_images": {"android": "assets/launch-image.png"}, "asset_dirs": ["assets"], - "extra_build_options": {"android": {"services": ["server:server.py"], "extra_permissions": []}} + "extra_build_options": {"android": {"services": ["server:server.py"], "extra_permissions": [], "sdk": 17}} }