Skip to content

Commit

Permalink
Webview: Drop USB APIs relating to Welcome screen
Browse files Browse the repository at this point in the history
Since the commit ("Welcome: Drop USB/SD option from Welcome screen") of
kolibri-explore-plugin [1] drops the USB option, Endless Key app removes
corresponding USB APIs accordingly.

[1]: https://github.com/endlessm/kolibri-explore-plugin

Fixes: #171
  • Loading branch information
starnight committed Aug 15, 2023
1 parent 4bab8c8 commit 02cf345
Show file tree
Hide file tree
Showing 4 changed files with 1 addition and 416 deletions.
6 changes: 1 addition & 5 deletions src/java/org/learningequality/KolibriAndroidHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void setAppKeyCookie(String url, String appKey) {

// Configure the WebView to allow fullscreen based on:
// https://stackoverflow.com/questions/15768837/playing-html5-video-on-fullscreen-in-android-webview/56186877#56186877
public void configure(final Runnable startWithNetwork, final Runnable startWithUSB, final Runnable loadingReady) {
public void configure(final Runnable startWithNetwork, final Runnable loadingReady) {
Log.i(TAG, "KolibriAndroidHelper configure");

mActivity.mOpenExternalLinksInBrowser = true;
Expand Down Expand Up @@ -108,10 +108,6 @@ public void startWithNetwork(String packId) {
Log.v(TAG, packId);
startWithNetwork.run();
}
@JavascriptInterface
public void startWithUSB() {
startWithUSB.run();
}
} , "WelcomeWrapper");

mLoadingWebView.getSettings().setAllowFileAccess(true);
Expand Down
304 changes: 0 additions & 304 deletions src/kolibri_android/android_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
import logging
import os
import re
import shutil
import stat
import sys
from contextlib import closing
from enum import auto
from enum import Enum
from functools import partial
from pathlib import Path
from queue import Queue
from urllib.parse import parse_qsl
from urllib.parse import urlparse

from android.activity import bind
from android.activity import unbind
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from jnius import autoclass
Expand Down Expand Up @@ -85,7 +81,6 @@

# Globals to keep references to Java objects
# See https://github.com/Android-for-Python/Android-for-Python-Users#pyjnius-memory-management
_choose_directory_intent = None
_notification_builder = None
_notification_intent = None
_send_intent = None
Expand All @@ -103,14 +98,6 @@ def _path_is_relative_to(self, *other):
Path.is_relative_to = _path_is_relative_to


class PermissionsCancelledError(Exception):
pass


class PermissionsWrongFolderError(Exception):
pass


def is_service_context():
return "PYTHON_SERVICE_ARGUMENT" in os.environ

Expand Down Expand Up @@ -196,76 +183,6 @@ def get_initial_content_pack_id():
return pack_id


def get_endless_key_uris():
preferences = get_preferences()
content_uri = preferences.getString("key_content_uri", None)
db_uri = preferences.getString("key_db_uri", None)
logger.debug("Stored Endless Key URIs: content=%s, db=%s", content_uri, db_uri)

if content_uri and db_uri:
return {"content": content_uri, "db": db_uri}

return None


def choose_endless_key_uris():
"""Call the file picker and validate the chosen folder"""
activity = get_activity()
data_uri = choose_directory(activity, msg="Select the KOLIBRI_DATA folder")

if data_uri is None:
logger.info("User cancelled Endless Key selection")
raise PermissionsCancelledError()

tree_uri = Uri.parse(data_uri)
tree_doc_id = DocumentsContract.getTreeDocumentId(tree_uri)
tree_doc_uri = DocumentsContract.buildDocumentUriUsingTree(tree_uri, tree_doc_id)

content_resolver = activity.getContentResolver()
tree_files = document_tree_list_files(tree_doc_uri, content_resolver)

content = tree_files.get("content")
if not content or content["mime_type"] != Document.MIME_TYPE_DIR:
logger.info("The selected folder does not contain a content folder")
raise PermissionsWrongFolderError()
content_uri = content["uri"].toString()

preseeded_home = tree_files.get("preseeded_kolibri_home")
if not preseeded_home or preseeded_home["mime_type"] != Document.MIME_TYPE_DIR:
logger.info(
"The selected folder does not contain a preseeded_kolibri_home folder"
)
raise PermissionsWrongFolderError()
preseeded_home_files = document_tree_list_files(
preseeded_home["uri"], content_resolver
)

db = preseeded_home_files.get("db.sqlite3")
if not db or ["mime_type"] == Document.MIME_TYPE_DIR:
logger.info(
"The selected folder does not contain a db.sqlite3 file in the preseeded_kolibri_home folder"
)
raise PermissionsWrongFolderError()
db_uri = db["uri"].toString()

logger.info("Found Endless Key URIs: content=%s, db=%s", content_uri, db_uri)
return {"content": content_uri, "db": db_uri}


def set_endless_key_uris(endless_key_uris):
if endless_key_uris is None:
return

content_uri = endless_key_uris["content"]
db_uri = endless_key_uris["db"]
if content_uri and db_uri:
logger.info("Setting Endless Key URIs: content=%s, db=%s", content_uri, db_uri)
editor = get_preferences().edit()
editor.putString("key_content_uri", content_uri)
editor.putString("key_db_uri", db_uri)
editor.commit()


def is_document_uri(path, context=None):
if not urlparse(path).scheme:
return False
Expand Down Expand Up @@ -305,12 +222,6 @@ def document_opener(path, flags, content_resolver=None):
return pfd.detachFd()


def open_document(uri, mode="r", **kwargs):
"""open wrapper using DocumentsContract opener"""
kwargs["opener"] = document_opener
return open(uri, mode=mode, **kwargs)


def open_file(path, mode="r", context=None, content_resolver=None, **kwargs):
if context is None:
context = get_activity()
Expand Down Expand Up @@ -443,214 +354,6 @@ def document_tree_join(tree_doc_uri, path, content_resolver=None):
return path_uri


def document_tree_list_files(tree_doc_uri, content_resolver=None):
if content_resolver is None:
content_resolver = get_activity().getContentResolver()

tree_doc_id = DocumentsContract.getDocumentId(tree_doc_uri)
children_uri = DocumentsContract.buildChildDocumentsUriUsingTree(
tree_doc_uri, tree_doc_id
)

columns = (
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_SIZE,
)
listing = {}
with closing(content_resolver.query(children_uri, columns, None, None)) as cursor:
while cursor.moveToNext():
doc_id = cursor.getString(1)
doc_uri = DocumentsContract.buildDocumentUriUsingTree(tree_doc_uri, doc_id)

listing[cursor.getString(0)] = {
"id": doc_id,
"uri": doc_uri,
"last_modified": cursor.getLong(2),
"mime_type": cursor.getString(3),
"size": cursor.getLong(4),
}

return listing


def provision_endless_key_database(endless_key_uris):
if endless_key_uris is not None:
home_folder = get_home_folder()
dst_path = os.path.join(home_folder, "db.sqlite3")
if os.path.exists(dst_path):
logger.debug("EK database already exists, skipping.")
return
if not os.path.exists(home_folder):
os.mkdir(home_folder)

src_uri = endless_key_uris["db"]
with open_document(src_uri, "rb") as src:
with open(dst_path, "wb") as dst:
# The file metadata on the database is irrelevant, so we
# only need to copy the content.
shutil.copyfileobj(src, dst)
logger.debug("EK database provisioned.")


def _get_directory_path(volume):
if SDK_INT < 30:
uuid = volume.getUuid()
if uuid is None:
return None
return os.path.join("/storage", uuid)
else:
directory_file = volume.getDirectory()
if directory_file is None:
return None
return directory_file.toString()


def _get_open_document_intent(volume):
# Compatibility with SDK 28 and eariler
# https://developer.android.com/sdk/api_diff/29/changes/android.os.storage.StorageVolume#android.os.storage.StorageVolume.createOpenDocumentTreeIntent_added()
if SDK_INT < 29:
return volume.createAccessIntent(None)
else:
return volume.createOpenDocumentTreeIntent()


def has_any_external_storage_device():
activity = get_activity()
storage_manager = activity.getSystemService(Context.STORAGE_SERVICE)

found = False

for volume in storage_manager.getStorageVolumes():
if volume is None or volume.getUuid() == MY_FILES_UUID:
continue

elif volume.isRemovable() and volume.getState() == "mounted":
found = True
break

return found


def create_open_kolibri_data_intent(context):
"""Create an ACTION_OPEN_DOCUMENT_TREE using KOLIBRI_DATA URI"""
storage_manager = context.getSystemService(Context.STORAGE_SERVICE)
for volume in storage_manager.getStorageVolumes():
if volume is None:
continue
state = volume.getState()
is_removable = volume.isRemovable()
uuid = volume.getUuid()
path = _get_directory_path(volume)
logger.debug(
"Found volume UUID=%s, state=%s, removable=%s, mount=%s",
uuid,
state,
is_removable,
path,
)

if not is_removable or state != "mounted" or uuid == MY_FILES_UUID:
continue

# Create an ACTION_OPEN_DOCUMENT_TREE intent with the URI of the
# volume root as the EXTRA_INITIAL_URI.
intent = _get_open_document_intent(volume)
intent_data = intent.getParcelableExtra(DocumentsContract.EXTRA_INITIAL_URI)

if not intent_data:
continue

# Extract the initial URI from the intent and then adjust it so
# it includes the expected KOLIBRI_DATA path. If that path
# doesn't exist, then the file picker will use the default
# internal storage root.
initial_uri = cast("android.net.Uri", intent_data)
logger.debug(
"Volume %s OPEN_DOCUMENT_TREE initial URI: %s", uuid, initial_uri.toString()
)
root_id = DocumentsContract.getRootId(initial_uri)
kolibri_data_id = f"{root_id}:KOLIBRI_DATA"
kolibri_data_uri = DocumentsContract.buildDocumentUri(
initial_uri.getAuthority(), kolibri_data_id
)
logger.debug(
"Volume %s OPEN_DOCUMENT_TREE KOLIBRI_DATA URI: %s",
uuid,
kolibri_data_uri.toString(),
)
intent.putExtra(
DocumentsContract.EXTRA_INITIAL_URI,
cast("android.os.Parcelable", kolibri_data_uri),
)

return intent

# No removable volume found, return the default OPEN_DOCUMENT_TREE
# intent.
return Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)


def choose_directory(activity=None, msg=None, timeout=None):
"""Run the file picker to choose a directory"""
global _choose_directory_intent

if activity is None:
activity = get_activity()
content_resolver = activity.getContentResolver()

data_queue = Queue(1)
OPEN_DIRECTORY_REQUEST_CODE = 0xF11E

def on_activity_result(request, result, intent):
if request != OPEN_DIRECTORY_REQUEST_CODE:
return

if result != Activity.RESULT_OK:
if result == Activity.RESULT_CANCELED:
logger.info("Open directory request cancelled")
else:
logger.info("Open directory request result %d", result)
data_queue.put(None, timeout=timeout)
return

if intent is None:
logger.warning("Open directory result contains no data")
data_queue.put(None, timeout=timeout)
return

uri = intent.getData()
uri_str = uri.toString()
logger.info("Open directory request returned URI %s", uri_str)

logger.debug("Persisting read permissions for %s", uri_str)
flags = intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION
content_resolver.takePersistableUriPermission(uri, flags)

data_queue.put(uri_str, timeout=timeout)

_choose_directory_intent = create_open_kolibri_data_intent(activity)
logger.info("Open directory intent: %s", _choose_directory_intent.toString())
extras = _choose_directory_intent.getExtras()
if extras:
logger.info("Open directory intent extras: %s", extras.toString())

bind(on_activity_result=on_activity_result)
try:
activity.startActivityForResult(
_choose_directory_intent,
OPEN_DIRECTORY_REQUEST_CODE,
)
if msg:
show_toast(activity, msg, Toast.LENGTH_LONG)
return data_queue.get(timeout=timeout)
finally:
unbind(on_activity_result=on_activity_result)
_choose_directory_intent = None


def show_toast(context, msg, duration):
"""Helper to create and show a Toast message"""

Expand Down Expand Up @@ -1033,15 +736,13 @@ def check_webview_version():

class StartupState(Enum):
FIRST_TIME = auto()
USB_USER = auto()
NETWORK_USER = auto()

@classmethod
def get_current_state(cls):
"""
Returns the current app startup state that could be:
* FIRST_TIME
* USB_USER
* NETWORK_USER
"""
home = get_home_folder()
Expand All @@ -1051,11 +752,6 @@ def get_current_state(cls):
if not os.path.exists(db_path):
return cls.FIRST_TIME

# If there are Endless Key URIs in the preferences, the app has
# been started with an Endless Key USB.
if get_endless_key_uris():
return cls.USB_USER

# in other case, the app is initialized but with content downloaded
# using the network
return cls.NETWORK_USER
Expand Down
Loading

0 comments on commit 02cf345

Please sign in to comment.