mirror of
https://github.com/markqvist/Sideband.git
synced 2025-12-15 08:38:47 -05:00
Implemented workaround for Kivy/SDL bug where on_resume is not dispatched on Android
This commit is contained in:
parent
42d859f96e
commit
9a3cda077c
6 changed files with 698 additions and 16 deletions
|
|
@ -28,6 +28,11 @@ patchsdl:
|
||||||
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/build/bootstrap_builds/sdl2/src/main/java/org/kivy/android/PythonService.java
|
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/build/bootstrap_builds/sdl2/src/main/java/org/kivy/android/PythonService.java
|
||||||
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/kivy/android/PythonService.java
|
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/kivy/android/PythonService.java
|
||||||
|
|
||||||
|
# Pach python activity
|
||||||
|
cp patches/PythonActivity.java .buildozer/android/platform/python-for-android/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
|
||||||
|
cp patches/PythonActivity.java .buildozer/android/platform/build-arm64-v8a/build/bootstrap_builds/sdl2/src/main/java/org/kivy/android/PythonActivity.java
|
||||||
|
cp patches/PythonActivity.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/kivy/android/PythonActivity.java
|
||||||
|
|
||||||
patchpycodec2:
|
patchpycodec2:
|
||||||
patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a/dists/sideband/_python_bundle__arm64-v8a/_python_bundle/site-packages/pycodec2/pycodec2.so
|
patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a/dists/sideband/_python_bundle__arm64-v8a/_python_bundle/site-packages/pycodec2/pycodec2.so
|
||||||
# patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a/dists/sideband/_python_bundle__armeabi-v7a/_python_bundle/site-packages/pycodec2/pycodec2.so
|
# patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a/dists/sideband/_python_bundle__armeabi-v7a/_python_bundle/site-packages/pycodec2/pycodec2.so
|
||||||
|
|
|
||||||
|
|
@ -405,6 +405,7 @@ class SidebandApp(MDApp):
|
||||||
self.icon = self.sideband.asset_dir+"/icon.png"
|
self.icon = self.sideband.asset_dir+"/icon.png"
|
||||||
self.notification_icon = self.sideband.asset_dir+"/notification_icon.png"
|
self.notification_icon = self.sideband.asset_dir+"/notification_icon.png"
|
||||||
|
|
||||||
|
self.resume_event_scheduler = None
|
||||||
self.connectivity_updater = None
|
self.connectivity_updater = None
|
||||||
self.last_map_update = 0
|
self.last_map_update = 0
|
||||||
self.last_telemetry_received = 0
|
self.last_telemetry_received = 0
|
||||||
|
|
@ -830,16 +831,18 @@ class SidebandApp(MDApp):
|
||||||
def on_pause(self):
|
def on_pause(self):
|
||||||
if self.sideband:
|
if self.sideband:
|
||||||
RNS.log("App pausing...", RNS.LOG_DEBUG)
|
RNS.log("App pausing...", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
if self.resume_event_scheduler == None:
|
||||||
|
self.resume_event_scheduler = Clock.schedule_interval(self.perform_paused_check, 0.1)
|
||||||
|
|
||||||
self.sideband.setstate("app.running", True)
|
self.sideband.setstate("app.running", True)
|
||||||
self.sideband.setstate("app.foreground", False)
|
self.sideband.setstate("app.foreground", False)
|
||||||
self.app_state = SidebandApp.PAUSED
|
self.app_state = SidebandApp.PAUSED
|
||||||
self.sideband.should_persist_data()
|
self.sideband.should_persist_data()
|
||||||
if self.conversations_view != None:
|
|
||||||
self.conversations_view.ids.conversations_scrollview.effect_cls = ScrollEffect
|
|
||||||
self.conversations_view.ids.conversations_scrollview.scroll = 1
|
|
||||||
|
|
||||||
RNS.log("App paused", RNS.LOG_DEBUG)
|
RNS.log("App paused", RNS.LOG_DEBUG)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -850,17 +853,10 @@ class SidebandApp(MDApp):
|
||||||
self.sideband.setstate("app.foreground", True)
|
self.sideband.setstate("app.foreground", True)
|
||||||
self.sideband.setstate("wants.clear_notifications", True)
|
self.sideband.setstate("wants.clear_notifications", True)
|
||||||
self.app_state = SidebandApp.ACTIVE
|
self.app_state = SidebandApp.ACTIVE
|
||||||
if self.conversations_view != None:
|
|
||||||
self.conversations_view.ids.conversations_scrollview.effect_cls = ScrollEffect
|
|
||||||
self.conversations_view.ids.conversations_scrollview.scroll = 1
|
|
||||||
|
|
||||||
else:
|
|
||||||
RNS.log("Conversations view did not exist", RNS.LOG_DEBUG)
|
|
||||||
|
|
||||||
def ui_update_job():
|
def ui_update_job():
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
def cb(dt):
|
def cb(dt): self.perform_wake_update()
|
||||||
self.perform_wake_update()
|
|
||||||
Clock.schedule_once(cb, 0.1)
|
Clock.schedule_once(cb, 0.1)
|
||||||
threading.Thread(target=ui_update_job, daemon=True).start()
|
threading.Thread(target=ui_update_job, daemon=True).start()
|
||||||
|
|
||||||
|
|
@ -879,6 +875,29 @@ class SidebandApp(MDApp):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def perform_paused_check(self, delta_time):
|
||||||
|
# This workaround mitigates yet another bug in Kivy
|
||||||
|
# on Android, where the JNI/Python bridge now for
|
||||||
|
# Lord knows whatever reason fails to dispatch the
|
||||||
|
# onResume event from the Android app lifecycle
|
||||||
|
# management API. So we have to resort to this hack
|
||||||
|
# of scheduling a manual check that reads a patched-
|
||||||
|
# in property on the JNI side of the app activity,
|
||||||
|
# and then "manually" dispatch on_resume.
|
||||||
|
if self.app_state == SidebandApp.PAUSED:
|
||||||
|
# Oh hai, we're running, but we should really
|
||||||
|
# be paused? What gives? Must mean we've been
|
||||||
|
# woken up again, but someone forgot to inform
|
||||||
|
# us about that. Let's have a look, shall we...
|
||||||
|
activity = autoclass('org.kivy.android.PythonActivity').mActivity
|
||||||
|
if activity.activityPaused == False:
|
||||||
|
# Who would have thought, the activity was
|
||||||
|
# resumed! Good thing we can play event-
|
||||||
|
# dispatch pretend ourselves.
|
||||||
|
Clock.unschedule(self.resume_event_scheduler)
|
||||||
|
self.resume_event_scheduler = None
|
||||||
|
self.on_resume()
|
||||||
|
|
||||||
def perform_wake_update(self):
|
def perform_wake_update(self):
|
||||||
# This workaround mitigates a bug in Kivy on Android
|
# This workaround mitigates a bug in Kivy on Android
|
||||||
# which causes the UI to turn black on app resume,
|
# which causes the UI to turn black on app resume,
|
||||||
|
|
|
||||||
646
sbapp/patches/PythonActivity.java
Normal file
646
sbapp/patches/PythonActivity.java
Normal file
|
|
@ -0,0 +1,646 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.content.res.Resources.NotFoundException;
|
||||||
|
|
||||||
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
import org.kivy.android.launcher.Project;
|
||||||
|
|
||||||
|
import org.renpy.android.ResourceManager;
|
||||||
|
|
||||||
|
|
||||||
|
public class PythonActivity extends SDLActivity {
|
||||||
|
private static final String TAG = "PythonActivity";
|
||||||
|
|
||||||
|
public static PythonActivity mActivity = null;
|
||||||
|
public boolean activityPaused = false;
|
||||||
|
|
||||||
|
private ResourceManager resourceManager = null;
|
||||||
|
private Bundle mMetaData = null;
|
||||||
|
private PowerManager.WakeLock mWakeLock = null;
|
||||||
|
|
||||||
|
public String getAppRoot() {
|
||||||
|
String app_root = getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
return app_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.v(TAG, "PythonActivity onCreate running");
|
||||||
|
resourceManager = new ResourceManager(this);
|
||||||
|
|
||||||
|
Log.v(TAG, "About to do super onCreate");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Log.v(TAG, "Did super onCreate");
|
||||||
|
|
||||||
|
this.mActivity = this;
|
||||||
|
this.showLoadingScreen(this.getLoadingScreen());
|
||||||
|
|
||||||
|
new UnpackFilesTask().execute(getAppRoot());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadLibraries() {
|
||||||
|
String app_root = new String(getAppRoot());
|
||||||
|
File app_root_file = new File(app_root);
|
||||||
|
PythonUtil.loadLibraries(app_root_file,
|
||||||
|
new File(getApplicationInfo().nativeLibraryDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error using a toast. (Only makes sense from non-UI
|
||||||
|
* threads.)
|
||||||
|
*/
|
||||||
|
public void toastError(final String msg) {
|
||||||
|
|
||||||
|
final Activity thisActivity = this;
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable () {
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait to show the error.
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
this.wait(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UnpackFilesTask extends AsyncTask<String, Void, String> {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
File app_root_file = new File(params[0]);
|
||||||
|
Log.v(TAG, "Ready to unpack");
|
||||||
|
PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
|
||||||
|
PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
// Figure out the directory where the game is. If the game was
|
||||||
|
// given to us via an intent, then we use the scheme-specific
|
||||||
|
// part of that intent to determine the file to launch. We
|
||||||
|
// also use the android.txt file to determine the orientation.
|
||||||
|
//
|
||||||
|
// Otherwise, we use the public data, if we have it, or the
|
||||||
|
// private data if we do not.
|
||||||
|
mActivity.finishLoad();
|
||||||
|
|
||||||
|
// finishLoad called setContentView with the SDL view, which
|
||||||
|
// removed the loading screen. However, we still need it to
|
||||||
|
// show until the app is ready to render, so pop it back up
|
||||||
|
// on top of the SDL view.
|
||||||
|
mActivity.showLoadingScreen(getLoadingScreen());
|
||||||
|
|
||||||
|
String app_root_dir = getAppRoot();
|
||||||
|
if (getIntent() != null && getIntent().getAction() != null &&
|
||||||
|
getIntent().getAction().equals("org.kivy.LAUNCH")) {
|
||||||
|
File path = new File(getIntent().getData().getSchemeSpecificPart());
|
||||||
|
|
||||||
|
Project p = Project.scanDirectory(path);
|
||||||
|
String entry_point = getEntryPoint(p.dir);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", p.dir + "/" + entry_point);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_ARGUMENT", p.dir);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_APP_PATH", p.dir);
|
||||||
|
|
||||||
|
if (p != null) {
|
||||||
|
if (p.landscape) {
|
||||||
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
|
} else {
|
||||||
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let old apps know they started.
|
||||||
|
try {
|
||||||
|
FileWriter f = new FileWriter(new File(path, ".launch"));
|
||||||
|
f.write("started");
|
||||||
|
f.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String entry_point = getEntryPoint(app_root_dir);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
|
||||||
|
Log.v(TAG, "Setting env vars for start.c and Python to use");
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
|
||||||
|
SDLActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
|
||||||
|
SDLActivity.nativeSetenv("PYTHONHOME", app_root_dir);
|
||||||
|
SDLActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
|
||||||
|
SDLActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "Access to our meta-data...");
|
||||||
|
mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
|
||||||
|
mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
|
||||||
|
|
||||||
|
PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
|
||||||
|
if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
|
||||||
|
mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
|
||||||
|
mActivity.mWakeLock.acquire();
|
||||||
|
}
|
||||||
|
if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) {
|
||||||
|
Log.v(TAG, "Surface will be transparent.");
|
||||||
|
getSurface().setZOrderOnTop(true);
|
||||||
|
getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Surface will NOT be transparent");
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch app if that hasn't been done yet:
|
||||||
|
if (mActivity.mHasFocus && (
|
||||||
|
// never went into proper resume state:
|
||||||
|
mActivity.mCurrentNativeState == NativeState.INIT ||
|
||||||
|
(
|
||||||
|
// resumed earlier but wasn't ready yet
|
||||||
|
mActivity.mCurrentNativeState == NativeState.RESUMED &&
|
||||||
|
mActivity.mSDLThread == null
|
||||||
|
))) {
|
||||||
|
// Because sometimes the app will get stuck here and never
|
||||||
|
// actually run, ensure that it gets launched if we're active:
|
||||||
|
mActivity.resumeNativeThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Void... values) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewGroup getLayout() {
|
||||||
|
return mLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SurfaceView getSurface() {
|
||||||
|
return mSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Listener interface for onNewIntent
|
||||||
|
//
|
||||||
|
|
||||||
|
public interface NewIntentListener {
|
||||||
|
void onNewIntent(Intent intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NewIntentListener> newIntentListeners = null;
|
||||||
|
|
||||||
|
public void registerNewIntentListener(NewIntentListener listener) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
|
||||||
|
this.newIntentListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterNewIntentListener(NewIntentListener listener) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
return;
|
||||||
|
this.newIntentListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
return;
|
||||||
|
this.onResume();
|
||||||
|
synchronized ( this.newIntentListeners ) {
|
||||||
|
Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();
|
||||||
|
while ( iterator.hasNext() ) {
|
||||||
|
(iterator.next()).onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Listener interface for onActivityResult
|
||||||
|
//
|
||||||
|
|
||||||
|
public interface ActivityResultListener {
|
||||||
|
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ActivityResultListener> activityResultListeners = null;
|
||||||
|
|
||||||
|
public void registerActivityResultListener(ActivityResultListener listener) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
|
||||||
|
this.activityResultListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterActivityResultListener(ActivityResultListener listener) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
return;
|
||||||
|
this.activityResultListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
return;
|
||||||
|
this.onResume();
|
||||||
|
synchronized ( this.activityResultListeners ) {
|
||||||
|
Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();
|
||||||
|
while ( iterator.hasNext() )
|
||||||
|
(iterator.next()).onActivityResult(requestCode, resultCode, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start_service(
|
||||||
|
String serviceTitle,
|
||||||
|
String serviceDescription,
|
||||||
|
String pythonServiceArgument
|
||||||
|
) {
|
||||||
|
_do_start_service(
|
||||||
|
serviceTitle, serviceDescription, pythonServiceArgument, true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start_service_not_as_foreground(
|
||||||
|
String serviceTitle,
|
||||||
|
String serviceDescription,
|
||||||
|
String pythonServiceArgument
|
||||||
|
) {
|
||||||
|
_do_start_service(
|
||||||
|
serviceTitle, serviceDescription, pythonServiceArgument, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void _do_start_service(
|
||||||
|
String serviceTitle,
|
||||||
|
String serviceDescription,
|
||||||
|
String pythonServiceArgument,
|
||||||
|
boolean showForegroundNotification
|
||||||
|
) {
|
||||||
|
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
|
||||||
|
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
|
||||||
|
String app_root_dir = PythonActivity.mActivity.getAppRoot();
|
||||||
|
String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
|
||||||
|
serviceIntent.putExtra("androidPrivate", argument);
|
||||||
|
serviceIntent.putExtra("androidArgument", app_root_dir);
|
||||||
|
serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
|
||||||
|
serviceIntent.putExtra("pythonName", "python");
|
||||||
|
serviceIntent.putExtra("pythonHome", app_root_dir);
|
||||||
|
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
|
||||||
|
serviceIntent.putExtra("serviceStartAsForeground",
|
||||||
|
(showForegroundNotification ? "true" : "false")
|
||||||
|
);
|
||||||
|
serviceIntent.putExtra("serviceTitle", serviceTitle);
|
||||||
|
serviceIntent.putExtra("serviceDescription", serviceDescription);
|
||||||
|
serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||||
|
PythonActivity.mActivity.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop_service() {
|
||||||
|
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
|
||||||
|
PythonActivity.mActivity.stopService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loading screen view **/
|
||||||
|
public static ImageView mImageView = null;
|
||||||
|
public static View mLottieView = null;
|
||||||
|
/** Whether main routine/actual app has started yet **/
|
||||||
|
protected boolean mAppConfirmedActive = false;
|
||||||
|
/** Timer for delayed loading screen removal. **/
|
||||||
|
protected Timer loadingScreenRemovalTimer = null;
|
||||||
|
|
||||||
|
// Overridden since it's called often, to check whether to remove the
|
||||||
|
// loading screen:
|
||||||
|
@Override
|
||||||
|
protected boolean sendCommand(int command, Object data) {
|
||||||
|
boolean result = super.sendCommand(command, data);
|
||||||
|
considerLoadingScreenRemoval();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Confirm that the app's main routine has been launched.
|
||||||
|
**/
|
||||||
|
@Override
|
||||||
|
public void appConfirmedActive() {
|
||||||
|
if (!mAppConfirmedActive) {
|
||||||
|
Log.v(TAG, "appConfirmedActive() -> preparing loading screen removal");
|
||||||
|
mAppConfirmedActive = true;
|
||||||
|
considerLoadingScreenRemoval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This is called from various places to check whether the app's main
|
||||||
|
* routine has been launched already, and if it has, then the loading
|
||||||
|
* screen will be removed.
|
||||||
|
**/
|
||||||
|
public void considerLoadingScreenRemoval() {
|
||||||
|
if (loadingScreenRemovalTimer != null)
|
||||||
|
return;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (((PythonActivity)PythonActivity.mSingleton).mAppConfirmedActive &&
|
||||||
|
loadingScreenRemovalTimer == null) {
|
||||||
|
// Remove loading screen but with a delay.
|
||||||
|
// (app can use p4a's android.loadingscreen module to
|
||||||
|
// do it quicker if it wants to)
|
||||||
|
// get a handler (call from main thread)
|
||||||
|
// this will run when timer elapses
|
||||||
|
TimerTask removalTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// post a runnable to the handler
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PythonActivity activity =
|
||||||
|
((PythonActivity)PythonActivity.mSingleton);
|
||||||
|
if (activity != null)
|
||||||
|
activity.removeLoadingScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadingScreenRemovalTimer = new Timer();
|
||||||
|
loadingScreenRemovalTimer.schedule(removalTask, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeLoadingScreen() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
View view = mLottieView != null ? mLottieView : mImageView;
|
||||||
|
if (view != null && view.getParent() != null) {
|
||||||
|
((ViewGroup)view.getParent()).removeView(view);
|
||||||
|
mLottieView = null;
|
||||||
|
mImageView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntryPoint(String search_dir) {
|
||||||
|
/* Get the main file (.pyc|.py) depending on if we
|
||||||
|
* have a compiled version or not.
|
||||||
|
*/
|
||||||
|
List<String> entryPoints = new ArrayList<String>();
|
||||||
|
entryPoints.add("main.pyc"); // python 3 compiled files
|
||||||
|
for (String value : entryPoints) {
|
||||||
|
File mainFile = new File(search_dir + "/" + value);
|
||||||
|
if (mainFile.exists()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "main.py";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showLoadingScreen(View view) {
|
||||||
|
try {
|
||||||
|
if (mLayout == null) {
|
||||||
|
setContentView(view);
|
||||||
|
} else if (view.getParent() == null) {
|
||||||
|
mLayout.addView(view);
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// The loading screen can be attempted to be applied twice if app
|
||||||
|
// is tabbed in/out, quickly.
|
||||||
|
// (Gives error "The specified child already has a parent.
|
||||||
|
// You must call removeView() on the child's parent first.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setBackgroundColor(View view) {
|
||||||
|
/*
|
||||||
|
* Set the presplash loading screen background color
|
||||||
|
* https://developer.android.com/reference/android/graphics/Color.html
|
||||||
|
* Parse the color string, and return the corresponding color-int.
|
||||||
|
* If the string cannot be parsed, throws an IllegalArgumentException exception.
|
||||||
|
* Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
|
||||||
|
* 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
|
||||||
|
* 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
|
||||||
|
* 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
|
||||||
|
*/
|
||||||
|
String backgroundColor = resourceManager.getString("presplash_color");
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
try {
|
||||||
|
view.setBackgroundColor(Color.parseColor(backgroundColor));
|
||||||
|
} catch (IllegalArgumentException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected View getLoadingScreen() {
|
||||||
|
// If we have an mLottieView or mImageView already, then do
|
||||||
|
// nothing because it will have already been made the content
|
||||||
|
// view or added to the layout.
|
||||||
|
if (mLottieView != null || mImageView != null) {
|
||||||
|
// we already have a splash screen
|
||||||
|
return mLottieView != null ? mLottieView : mImageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first try to load the lottie one
|
||||||
|
try {
|
||||||
|
mLottieView = getLayoutInflater().inflate(
|
||||||
|
this.resourceManager.getIdentifier("lottie", "layout"),
|
||||||
|
mLayout,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
if (mLayout == null) {
|
||||||
|
setContentView(mLottieView);
|
||||||
|
} else if (PythonActivity.mLottieView.getParent() == null) {
|
||||||
|
mLayout.addView(mLottieView);
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// The loading screen can be attempted to be applied twice if app
|
||||||
|
// is tabbed in/out, quickly.
|
||||||
|
// (Gives error "The specified child already has a parent.
|
||||||
|
// You must call removeView() on the child's parent first.")
|
||||||
|
}
|
||||||
|
setBackgroundColor(mLottieView);
|
||||||
|
return mLottieView;
|
||||||
|
}
|
||||||
|
catch (NotFoundException e) {
|
||||||
|
Log.v("SDL", "couldn't find lottie layout or animation, trying static splash");
|
||||||
|
}
|
||||||
|
|
||||||
|
// no lottie asset, try to load the static image then
|
||||||
|
int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
|
||||||
|
InputStream is = this.getResources().openRawResource(presplashId);
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
try {
|
||||||
|
bitmap = BitmapFactory.decodeStream(is);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mImageView = new ImageView(this);
|
||||||
|
mImageView.setImageBitmap(bitmap);
|
||||||
|
setBackgroundColor(mImageView);
|
||||||
|
|
||||||
|
mImageView.setLayoutParams(new ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.FILL_PARENT,
|
||||||
|
ViewGroup.LayoutParams.FILL_PARENT));
|
||||||
|
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
|
return mImageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
if (this.mWakeLock != null && mWakeLock.isHeld()) {
|
||||||
|
this.mWakeLock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activityPaused = true;
|
||||||
|
Log.v(TAG, "onPause()");
|
||||||
|
try {
|
||||||
|
super.onPause();
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
// Catch pause while still in loading screen failing to
|
||||||
|
// call native function (since it's not yet loaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
if (this.mWakeLock != null) {
|
||||||
|
this.mWakeLock.acquire();
|
||||||
|
}
|
||||||
|
this.activityPaused = false;
|
||||||
|
Log.v(TAG, "onResume() in PythonActivity");
|
||||||
|
try {
|
||||||
|
super.onResume();
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
// Catch resume while still in loading screen failing to
|
||||||
|
// call native function (since it's not yet loaded)
|
||||||
|
}
|
||||||
|
considerLoadingScreenRemoval();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
try {
|
||||||
|
super.onWindowFocusChanged(hasFocus);
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
// Catch window focus while still in loading screen failing to
|
||||||
|
// call native function (since it's not yet loaded)
|
||||||
|
}
|
||||||
|
considerLoadingScreenRemoval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by android.permissions p4a module to register a call back after
|
||||||
|
* requesting runtime permissions
|
||||||
|
**/
|
||||||
|
public interface PermissionsCallback {
|
||||||
|
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionsCallback permissionCallback;
|
||||||
|
private boolean havePermissionsCallback = false;
|
||||||
|
|
||||||
|
public void addPermissionsCallback(PermissionsCallback callback) {
|
||||||
|
permissionCallback = callback;
|
||||||
|
havePermissionsCallback = true;
|
||||||
|
Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
Log.v(TAG, "onRequestPermissionsResult()");
|
||||||
|
if (havePermissionsCallback) {
|
||||||
|
Log.v(TAG, "onRequestPermissionsResult passed to callback");
|
||||||
|
permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by android.permissions p4a module to check a permission
|
||||||
|
**/
|
||||||
|
public boolean checkCurrentPermission(String permission) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < 23)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method methodCheckPermission =
|
||||||
|
Activity.class.getMethod("checkSelfPermission", String.class);
|
||||||
|
Object resultObj = methodCheckPermission.invoke(this, permission);
|
||||||
|
int result = Integer.parseInt(resultObj.toString());
|
||||||
|
if (result == PackageManager.PERMISSION_GRANTED)
|
||||||
|
return true;
|
||||||
|
} catch (IllegalAccessException | NoSuchMethodException |
|
||||||
|
InvocationTargetException e) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by android.permissions p4a module to request runtime permissions
|
||||||
|
**/
|
||||||
|
public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < 23)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method methodRequestPermission =
|
||||||
|
Activity.class.getMethod("requestPermissions",
|
||||||
|
String[].class, int.class);
|
||||||
|
methodRequestPermission.invoke(this, permissions, requestCode);
|
||||||
|
} catch (IllegalAccessException | NoSuchMethodException |
|
||||||
|
InvocationTargetException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestPermissions(String[] permissions) {
|
||||||
|
requestPermissionsWithRequestCode(permissions, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void changeKeyboard(int inputType) {
|
||||||
|
if (SDLActivity.keyboardInputType != inputType){
|
||||||
|
SDLActivity.keyboardInputType = inputType;
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.restartInput(mTextEdit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4204,6 +4204,7 @@ class SidebandCore():
|
||||||
self.interface_tcp = None
|
self.interface_tcp = None
|
||||||
self.interface_i2p = None
|
self.interface_i2p = None
|
||||||
self.interface_rnode = None
|
self.interface_rnode = None
|
||||||
|
self.interface_weave = None
|
||||||
self.interface_modem = None
|
self.interface_modem = None
|
||||||
self.interface_serial = None
|
self.interface_serial = None
|
||||||
|
|
||||||
|
|
@ -4326,7 +4327,7 @@ class SidebandCore():
|
||||||
self.setstate("init.loadingstate", "Starting RNode")
|
self.setstate("init.loadingstate", "Starting RNode")
|
||||||
self.__add_rnodeinterface()
|
self.__add_rnodeinterface()
|
||||||
|
|
||||||
if self.config["connect_weave"] or True:
|
if self.config["connect_weave"]:
|
||||||
self.setstate("init.loadingstate", "Starting Weave")
|
self.setstate("init.loadingstate", "Starting Weave")
|
||||||
self.__add_weaveinterface()
|
self.__add_weaveinterface()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,7 @@ Builder.load_string("""
|
||||||
icon: "dots-vertical"
|
icon: "dots-vertical"
|
||||||
|
|
||||||
<ConversationList>:
|
<ConversationList>:
|
||||||
|
id: conversations_scrollview
|
||||||
viewclass: "ConversationEntry"
|
viewclass: "ConversationEntry"
|
||||||
effect_cls: "ScrollEffect"
|
effect_cls: "ScrollEffect"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,9 @@ MDNavigationLayout:
|
||||||
|
|
||||||
|
|
||||||
OneLineIconListItem:
|
OneLineIconListItem:
|
||||||
text: "Encryption Keys"
|
text: "Backup & Keys"
|
||||||
on_release: root.ids.screen_manager.app.keys_action(self)
|
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||||
|
|
||||||
IconLeftWidget:
|
IconLeftWidget:
|
||||||
icon: "key-chain"
|
icon: "key-chain"
|
||||||
on_release: root.ids.screen_manager.app.keys_action(self)
|
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||||
|
|
@ -1131,7 +1131,7 @@ MDScreen:
|
||||||
orientation: "vertical"
|
orientation: "vertical"
|
||||||
|
|
||||||
MDTopAppBar:
|
MDTopAppBar:
|
||||||
title: "Encryption Keys"
|
title: "Backup & Keys"
|
||||||
anchor_title: "left"
|
anchor_title: "left"
|
||||||
elevation: 0
|
elevation: 0
|
||||||
left_action_items:
|
left_action_items:
|
||||||
|
|
@ -1160,6 +1160,16 @@ MDScreen:
|
||||||
text_size: self.width, None
|
text_size: self.width, None
|
||||||
height: self.texture_size[1]
|
height: self.texture_size[1]
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: keys_backup
|
||||||
|
icon: "home-export-outline"
|
||||||
|
text: "Backup Sideband Profile"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.app.profile_backup_action(self)
|
||||||
|
|
||||||
MDRectangleFlatIconButton:
|
MDRectangleFlatIconButton:
|
||||||
id: keys_display
|
id: keys_display
|
||||||
icon: "eye"
|
icon: "eye"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue