Skip to content

Commit

Permalink
store only information necessary to avoid need to store context obj
Browse files Browse the repository at this point in the history
  • Loading branch information
bjester committed Feb 2, 2024
1 parent 0296b28 commit f38e5f9
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,64 @@
package org.kivy.android;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import android.provider.Settings;

import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

public class PythonContext {
public static final String PACKAGE = "org.learningequality.Kolibri";
public static PythonContext mInstance;

private final Context context;
private final ConnectivityManager connectivityManager;
private final ConnectivityManager.NetworkCallback networkCallback;
private final AtomicBoolean isMetered = new AtomicBoolean(false);
private final String externalFilesDir;
private final String versionName;
private final String certificateInfo;
private final String nodeId;

private PythonContext(Context context) {
this.context = context;
this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

this.networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
isMetered.set(connectivityManager.isActiveNetworkMetered());
}

@Override
public void onLost(Network network) {
super.onLost(network);
isMetered.set(false);
}
};

NetworkRequest networkRequest = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();

this.connectivityManager.registerNetworkCallback(networkRequest, this.networkCallback);

this.externalFilesDir = context.getExternalFilesDir(null).toString();

PackageInfo packageInfo = getPackageInfo();
this.versionName = packageInfo.versionName;

PackageInfo certificateInfo = getPackageInfo(PackageManager.GET_SIGNATURES);
this.certificateInfo = certificateInfo.signatures[0].toCharsString();

this.nodeId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}

public static PythonContext getInstance(Context context) {
Expand All @@ -24,10 +74,74 @@ public static PythonContext getInstance(Context context) {
return PythonContext.mInstance;
}

// TODO: remove this, and don't store context on the class
public static Context get() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.context;
}

public static String getLocale() {
return Locale.getDefault().toLanguageTag();
}

public static Boolean isActiveNetworkMetered() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.isMetered.get();
}

public static String getExternalFilesDir() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.externalFilesDir;
}

public static String getVersionName() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.versionName;
}

public static String getCertificateInfo() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.certificateInfo;
}

public static String getNodeId() {
if (PythonContext.mInstance == null) {
return null;
}
return PythonContext.mInstance.nodeId;
}

public static void destroy() {
if (PythonContext.mInstance != null) {
PythonContext.mInstance.connectivityManager.unregisterNetworkCallback(PythonContext.mInstance.networkCallback);
PythonContext.mInstance = null;
}
}

protected PackageInfo getPackageInfo() {
return getPackageInfo(0);
}

protected PackageInfo getPackageInfo(int flags) {
PackageManager packageManager = context.getPackageManager();
try {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return packageManager.getPackageInfo(PACKAGE, PackageManager.PackageInfoFlags.of(flags));
} else {
return packageManager.getPackageInfo(PACKAGE, flags);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Kolibri is not installed");
}
}
}
106 changes: 15 additions & 91 deletions src/android_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
import os
import re
from functools import cache
Expand All @@ -10,41 +9,19 @@
from jnius import cast


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


def is_taskworker_context():
return "PYTHON_WORKER_ARGUMENT" in os.environ


def get_timezone_name():
Timezone = autoclass("java.util.TimeZone")
return Timezone.getDefault().getDisplayName()


def start_service(service_name, service_args=None):
PythonActivity = autoclass("org.kivy.android.PythonActivity")
service_args = service_args or {}
service = autoclass(
"org.learningequality.Kolibri.Service{}".format(service_name.title())
)
service.start(PythonActivity.mActivity, json.dumps(dict(service_args)))


def get_service_args():
assert (
is_service_context()
), "Cannot get service args, as we are not in a service context."
return json.loads(os.environ.get("PYTHON_SERVICE_ARGUMENT") or "{}")


def get_package_info(package_name="org.learningequality.Kolibri", flags=0):
return get_context().getPackageManager().getPackageInfo(package_name, flags)
def get_version_name():
PythonContext = autoclass("org.kivy.android.PythonContext")
return PythonContext.getVersionName()


def get_version_name():
return get_package_info().versionName
def get_node_id():
PythonContext = autoclass("org.kivy.android.PythonContext")
return PythonContext.getNodeId()


@cache
Expand All @@ -55,7 +32,8 @@ def get_context():

@cache
def get_external_files_dir():
return get_context().getExternalFilesDir(None).toString()
PythonContext = autoclass("org.kivy.android.PythonContext")
return PythonContext.getExternalFilesDir()


# TODO: check for storage availability, allow user to chose sd card or internal
Expand Down Expand Up @@ -145,61 +123,11 @@ def share_by_intent(path=None, filename=None, message=None, app=None, mimetype=N
get_context().startActivity(sendIntent)


def make_service_foreground(title, message):
service = autoclass("org.kivy.android.PythonService").mService
Drawable = autoclass("{}.R$drawable".format(service.getPackageName()))
app_context = service.getApplication().getApplicationContext()

ANDROID_VERSION = autoclass("android.os.Build$VERSION")
SDK_INT = ANDROID_VERSION.SDK_INT
AndroidString = autoclass("java.lang.String")
Context = autoclass("android.content.Context")
Intent = autoclass("android.content.Intent")
NotificationBuilder = autoclass("android.app.Notification$Builder")
NotificationManager = autoclass("android.app.NotificationManager")
PendingIntent = autoclass("android.app.PendingIntent")
PythonActivity = autoclass("org.kivy.android.PythonActivity")

if SDK_INT >= 26:
NotificationChannel = autoclass("android.app.NotificationChannel")
notification_service = cast(
NotificationManager,
get_context().getSystemService(Context.NOTIFICATION_SERVICE),
)
channel_id = get_context().getPackageName()
app_channel = NotificationChannel(
channel_id,
"Kolibri Background Server",
NotificationManager.IMPORTANCE_DEFAULT,
)
notification_service.createNotificationChannel(app_channel)
notification_builder = NotificationBuilder(app_context, channel_id)
else:
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)


def get_signature_key_issuer():
PackageManager = autoclass("android.content.pm.PackageManager")
signature = get_package_info(flags=PackageManager.GET_SIGNATURES).signatures[0]
PythonContext = autoclass("org.kivy.android.PythonContext")
signature = PythonContext.getCertificateInfo()
cert = x509.load_der_x509_certificate(
signature.toByteArray().tostring(), default_backend()
signature, default_backend()
)

return cert.issuer.rfc4514_string()
Expand All @@ -222,17 +150,13 @@ def get_dummy_user_name():
cache_key = "DUMMY_USER_NAME"
value = value_cache.get(cache_key)
if value is None:
Locale = autoclass("java.util.Locale")
currentLocale = Locale.getDefault().toLanguageTag()
PythonContext = autoclass("org.kivy.android.PythonContext")
currentLocale = PythonContext.getLocale()
value = get_string("Learner", currentLocale)
value_cache.set(cache_key, value)
return value


def is_active_network_metered():
ConnectivityManager = autoclass("android.net.ConnectivityManager")

return cast(
ConnectivityManager,
get_context().getSystemService(get_context().CONNECTIVITY_SERVICE),
).isActiveNetworkMetered()
PythonContext = autoclass("org.kivy.android.PythonContext")
return PythonContext.isActiveNetworkMetered()
6 changes: 2 additions & 4 deletions src/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import kolibri # noqa: F401 Import Kolibri here so we can import modules from dist folder
import monkey_patch_zeroconf # noqa: F401 Import this to patch zeroconf
from android_utils import get_context
from android_utils import get_home_folder
from android_utils import get_node_id
from android_utils import get_signature_key_issuing_organization
from android_utils import get_timezone_name
from android_utils import get_version_name
from jnius import autoclass

script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(script_dir)
Expand Down Expand Up @@ -38,8 +37,7 @@


def set_node_id():
Secure = autoclass("android.provider.Settings$Secure")
node_id = Secure.getString(get_context().getContentResolver(), Secure.ANDROID_ID)
node_id = get_node_id()

# Don't set this if the retrieved id is falsy, too short, or a specific
# id that is known to be hardcoded in many devices.
Expand Down

0 comments on commit f38e5f9

Please sign in to comment.