mirror of
https://github.com/markqvist/Sideband.git
synced 2025-11-08 18:32:55 -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/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:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -405,6 +405,7 @@ class SidebandApp(MDApp):
|
|||
self.icon = self.sideband.asset_dir+"/icon.png"
|
||||
self.notification_icon = self.sideband.asset_dir+"/notification_icon.png"
|
||||
|
||||
self.resume_event_scheduler = None
|
||||
self.connectivity_updater = None
|
||||
self.last_map_update = 0
|
||||
self.last_telemetry_received = 0
|
||||
|
|
@ -830,16 +831,18 @@ class SidebandApp(MDApp):
|
|||
def on_pause(self):
|
||||
if self.sideband:
|
||||
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.foreground", False)
|
||||
self.app_state = SidebandApp.PAUSED
|
||||
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)
|
||||
return True
|
||||
|
||||
else:
|
||||
return True
|
||||
|
||||
|
|
@ -850,17 +853,10 @@ class SidebandApp(MDApp):
|
|||
self.sideband.setstate("app.foreground", True)
|
||||
self.sideband.setstate("wants.clear_notifications", True)
|
||||
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():
|
||||
time.sleep(0.05)
|
||||
def cb(dt):
|
||||
self.perform_wake_update()
|
||||
def cb(dt): self.perform_wake_update()
|
||||
Clock.schedule_once(cb, 0.1)
|
||||
threading.Thread(target=ui_update_job, daemon=True).start()
|
||||
|
||||
|
|
@ -879,6 +875,29 @@ class SidebandApp(MDApp):
|
|||
else:
|
||||
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):
|
||||
# This workaround mitigates a bug in Kivy on Android
|
||||
# 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_i2p = None
|
||||
self.interface_rnode = None
|
||||
self.interface_weave = None
|
||||
self.interface_modem = None
|
||||
self.interface_serial = None
|
||||
|
||||
|
|
@ -4326,7 +4327,7 @@ class SidebandCore():
|
|||
self.setstate("init.loadingstate", "Starting RNode")
|
||||
self.__add_rnodeinterface()
|
||||
|
||||
if self.config["connect_weave"] or True:
|
||||
if self.config["connect_weave"]:
|
||||
self.setstate("init.loadingstate", "Starting Weave")
|
||||
self.__add_weaveinterface()
|
||||
|
||||
|
|
|
|||
|
|
@ -489,6 +489,7 @@ Builder.load_string("""
|
|||
icon: "dots-vertical"
|
||||
|
||||
<ConversationList>:
|
||||
id: conversations_scrollview
|
||||
viewclass: "ConversationEntry"
|
||||
effect_cls: "ScrollEffect"
|
||||
|
||||
|
|
|
|||
|
|
@ -163,9 +163,9 @@ MDNavigationLayout:
|
|||
|
||||
|
||||
OneLineIconListItem:
|
||||
text: "Encryption Keys"
|
||||
text: "Backup & Keys"
|
||||
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||
|
||||
|
||||
IconLeftWidget:
|
||||
icon: "key-chain"
|
||||
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||
|
|
@ -1131,7 +1131,7 @@ MDScreen:
|
|||
orientation: "vertical"
|
||||
|
||||
MDTopAppBar:
|
||||
title: "Encryption Keys"
|
||||
title: "Backup & Keys"
|
||||
anchor_title: "left"
|
||||
elevation: 0
|
||||
left_action_items:
|
||||
|
|
@ -1160,6 +1160,16 @@ MDScreen:
|
|||
text_size: self.width, None
|
||||
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:
|
||||
id: keys_display
|
||||
icon: "eye"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue