Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small tweaks and cleanup #196

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import java.io.File;

/**
* Ideally this would be called `PythonWorkerImpl` but the name is used in the native code.
* Worker implementation that executes Python code.
*
* Ideally this would be called `PythonWorkerImpl` but the name is used in the native
* python-for-android code.
*/
public class PythonWorker {
private static final String TAG = "PythonWorkerImpl";
// Python environment variables
private final Context context;
private final String pythonName;
private final String workerEntrypoint;
private final String androidPrivate;
Expand All @@ -22,7 +26,7 @@ public class PythonWorker {
private final String pythonPath;

public PythonWorker(@NonNull Context context, String pythonName, String workerEntrypoint) {
PythonLoader.doLoad(context);
this.context = context;
this.pythonName = pythonName;
this.workerEntrypoint = workerEntrypoint;

Expand All @@ -33,6 +37,15 @@ public PythonWorker(@NonNull Context context, String pythonName, String workerEn
pythonPath = appRoot + ":" + appRoot + "/lib";
}

/**
* Prepare the Python environment.
*
* This should be called before any calls to `execute`.
*/
public void prepare() {
PythonLoader.doLoad(context);
}

// Native part
public static native int nativeStart(
String androidPrivate, String androidArgument,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.learningequality;

import android.app.ActivityManager;
import android.content.Context;
import android.os.Process;

import org.kivy.android.PythonActivity;
import org.learningequality.Kolibri.WorkerService;
Expand All @@ -23,4 +25,21 @@ public static boolean isActivityContext() {
public static boolean isServiceContext() {
return WorkerService.mService != null;
}

/**
* Get the name of the current process.
*
* @param context - the context to use
* @return the name of the current process as a string, or an empty string if not found
*/
public static String getCurrentProcessName(Context context) {
int pid = Process.myPid();
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import androidx.work.Configuration;

import org.kivy.android.PythonContext;
import org.learningequality.ContextUtil;
import org.learningequality.notification.NotificationRef;

import java.util.concurrent.Executors;
Expand All @@ -27,8 +28,12 @@ public void onCreate() {
// Initialize Python context
PythonContext.getInstance(this);
createNotificationChannels();
// Register activity lifecycle callbacks
registerActivityLifecycleCallbacks(new KolibriActivityLifecycleCallbacks());

String currentProcessName = ContextUtil.getCurrentProcessName(this);
if (currentProcessName.endsWith(getString(R.string.task_worker_process))) {
// Register activity lifecycle callbacks
registerActivityLifecycleCallbacks(new KolibriActivityLifecycleCallbacks());
}
WorkController.getInstance(this).wake();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;

import org.learningequality.task.Worker;
Expand All @@ -23,6 +24,10 @@ public BackgroundWorker(
) {
super(context, workerParams);
workerImpl = new PythonWorker(context, "TaskWorker", "taskworker.py");

// Ideally we wouldn't call this in the constructor, but we can't override `startWork` to
// call it just before `doWork` is called.
workerImpl.prepare();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public ListenableFuture<Result> startRemoteWork() {
final String id = getId().toString();
final String arg = getArgument();

workerImpl.prepare();

// See executor defined in configuration
final ThreadPoolExecutor executor = (ThreadPoolExecutor) getBackgroundExecutor();
// This is somewhat similar to what the plain `Worker` class does, except that we
Expand Down
Loading
Loading