summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuel Mira <samuel.mira@qt.io>2022-04-20 16:08:36 +0300
committerSamuel Mira <samuel.mira@qt.io>2022-04-27 19:55:33 +0000
commit5fd6704091febcc4abbc8d7ce06a393572524fa5 (patch)
tree0b31e9bf8849ce2f02fa0270b72d6edc70edebe3
parent41d217829cc1c34ae7c2fee92316a91a77018f00 (diff)
Fix restart QtActivity
Previously, a restart of QtActivity on Android would make the application fail with a blank screen. That happened because the QtActivity tried to reload the whole application and failed. With this patch, the QtActivity detects if the application is restarting by checking if QtNative and QtActivityDelegate are live and updates the connections on those objects accordingly. It allows the application to continue as before. In case that is not possible, the QtActivity will restart the application. Fixes: QTBUG-38971 Fixes: QTBUG-102298 Pick-to: 5.15 6.2 6.3 Change-Id: Id500d20b185d57b39d45d34eeaa99745a3c2b95b Reviewed-by: Ivan Solovev <ivan.solovev@qt.io> Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java168
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtNative.java12
-rw-r--r--src/android/java/src/org/qtproject/qt/android/bindings/QtActivityLoader.java33
-rw-r--r--src/corelib/kernel/qjnihelpers.cpp42
4 files changed, 176 insertions, 79 deletions
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java
index c6ae62273e..24098de241 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java
@@ -594,6 +594,100 @@ public class QtActivityDelegate
}
}
+ private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener()
+ {
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ private boolean isSimilarRotation(int r1, int r2)
+ {
+ return (r1 == r2)
+ || (r1 == Surface.ROTATION_0 && r2 == Surface.ROTATION_180)
+ || (r1 == Surface.ROTATION_180 && r2 == Surface.ROTATION_0)
+ || (r1 == Surface.ROTATION_90 && r2 == Surface.ROTATION_270)
+ || (r1 == Surface.ROTATION_270 && r2 == Surface.ROTATION_90);
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId)
+ {
+ Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
+ ? m_activity.getWindowManager().getDefaultDisplay()
+ : m_activity.getDisplay();
+ m_currentRotation = display.getRotation();
+ m_layout.setActivityDisplayRotation(m_currentRotation);
+ // Process orientation change only if it comes after the size
+ // change, or if the screen is rotated by 180 degrees.
+ // Otherwise it will be processed in QtLayout.
+ if (isSimilarRotation(m_currentRotation, m_layout.displayRotation()))
+ QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
+
+ float refreshRate = display.getRefreshRate();
+ QtNative.handleRefreshRateChanged(refreshRate);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+ };
+
+ public boolean updateActivity(Activity activity)
+ {
+ try {
+ // set new activity
+ loadActivity(activity);
+
+ // update the new activity content view to old layout
+ ViewGroup layoutParent = (ViewGroup)m_layout.getParent();
+ if (layoutParent != null)
+ layoutParent.removeView(m_layout);
+
+ m_activity.setContentView(m_layout);
+
+ // force c++ native activity object to update
+ return QtNative.updateNativeActivity();
+ } catch (Exception e) {
+ Log.w(QtNative.QtTAG, "Failed to update the activity.");
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ private void loadActivity(Activity activity)
+ throws NoSuchMethodException, PackageManager.NameNotFoundException
+ {
+ m_activity = activity;
+
+ QtNative.setActivity(m_activity, this);
+ setActionBarVisibility(false);
+
+ Class<?> activityClass = m_activity.getClass();
+ m_super_dispatchKeyEvent =
+ activityClass.getMethod("super_dispatchKeyEvent", KeyEvent.class);
+ m_super_onRestoreInstanceState =
+ activityClass.getMethod("super_onRestoreInstanceState", Bundle.class);
+ m_super_onRetainNonConfigurationInstance =
+ activityClass.getMethod("super_onRetainNonConfigurationInstance");
+ m_super_onSaveInstanceState =
+ activityClass.getMethod("super_onSaveInstanceState", Bundle.class);
+ m_super_onKeyDown =
+ activityClass.getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
+ m_super_onKeyUp =
+ activityClass.getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
+ m_super_onConfigurationChanged =
+ activityClass.getMethod("super_onConfigurationChanged", Configuration.class);
+ m_super_onActivityResult =
+ activityClass.getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
+ m_super_onWindowFocusChanged =
+ activityClass.getMethod("super_onWindowFocusChanged", Boolean.TYPE);
+ m_super_dispatchGenericMotionEvent =
+ activityClass.getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
+
+ m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
+
+ DisplayManager displayManager = (DisplayManager)m_activity.getSystemService(Context.DISPLAY_SERVICE);
+ displayManager.registerDisplayListener(displayListener, null);
+ }
+
public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams)
{
/// check parameters integrity
@@ -603,10 +697,14 @@ public class QtActivityDelegate
return false;
}
- m_activity = activity;
- setActionBarVisibility(false);
- QtNative.setActivity(m_activity, this);
- QtNative.setClassLoader(classLoader);
+ try {
+ loadActivity(activity);
+ QtNative.setClassLoader(classLoader);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+
if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
for (String className: Objects.requireNonNull(loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY))) {
if (className.length() == 0)
@@ -651,22 +749,6 @@ public class QtActivityDelegate
loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY));
}
- try {
- m_super_dispatchKeyEvent = m_activity.getClass().getMethod("super_dispatchKeyEvent", KeyEvent.class);
- m_super_onRestoreInstanceState = m_activity.getClass().getMethod("super_onRestoreInstanceState", Bundle.class);
- m_super_onRetainNonConfigurationInstance = m_activity.getClass().getMethod("super_onRetainNonConfigurationInstance");
- m_super_onSaveInstanceState = m_activity.getClass().getMethod("super_onSaveInstanceState", Bundle.class);
- m_super_onKeyDown = m_activity.getClass().getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
- m_super_onKeyUp = m_activity.getClass().getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
- m_super_onConfigurationChanged = m_activity.getClass().getMethod("super_onConfigurationChanged", Configuration.class);
- m_super_onActivityResult = m_activity.getClass().getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
- m_super_onWindowFocusChanged = m_activity.getClass().getMethod("super_onWindowFocusChanged", Boolean.TYPE);
- m_super_dispatchGenericMotionEvent = m_activity.getClass().getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
-
QtNative.setEnvironmentVariables(loaderParams.getString(ENVIRONMENT_VARIABLES_KEY));
QtNative.setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE",
"Droid Sans Mono;Droid Sans;Droid Sans Fallback");
@@ -683,52 +765,6 @@ public class QtActivityDelegate
else
m_applicationParameters = "";
- try {
- m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) { }
-
- private boolean isSimilarRotation(int r1, int r2)
- {
- return (r1 == r2)
- || (r1 == Surface.ROTATION_0 && r2 == Surface.ROTATION_180)
- || (r1 == Surface.ROTATION_180 && r2 == Surface.ROTATION_0)
- || (r1 == Surface.ROTATION_90 && r2 == Surface.ROTATION_270)
- || (r1 == Surface.ROTATION_270 && r2 == Surface.ROTATION_90);
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
- ? m_activity.getWindowManager().getDefaultDisplay()
- : m_activity.getDisplay();
- m_currentRotation = display.getRotation();
- m_layout.setActivityDisplayRotation(m_currentRotation);
- // Process orientation change only if it comes after the size
- // change, or if the screen is rotated by 180 degrees.
- // Otherwise it will be processed in QtLayout.
- if (isSimilarRotation(m_currentRotation, m_layout.displayRotation()))
- QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
- float refreshRate = display.getRefreshRate();
- QtNative.handleRefreshRateChanged(refreshRate);
- }
-
- @Override
- public void onDisplayRemoved(int displayId) { }
- };
-
- try {
- DisplayManager displayManager = (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
- displayManager.registerDisplayListener(displayListener, null);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
m_mainLib = QtNative.loadMainLibrary(m_mainLib, nativeLibsDir);
return m_mainLib != null;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java
index eb398a7061..e750159d8c 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java
@@ -129,6 +129,13 @@ public class QtNative
}
};
+ public static boolean isStarted()
+ {
+ boolean hasActivity = m_activity != null && m_activityDelegate != null;
+ boolean hasService = m_service != null && m_serviceDelegate != null;
+ return m_started && (hasActivity || hasService);
+ }
+
private static ClassLoader m_classLoader = null;
public static ClassLoader classLoader()
{
@@ -700,9 +707,10 @@ public class QtNative
public static native void quitQtCoreApplication();
public static native void quitQtAndroidPlugin();
public static native void terminateQt();
+ public static native boolean updateNativeActivity();
// application methods
- private static void quitApp()
+ public static void quitApp()
{
runAction(new Runnable() {
@Override
@@ -712,6 +720,8 @@ public class QtNative
m_activity.finish();
if (m_service != null)
m_service.stopSelf();
+
+ m_started = false;
}
});
}
diff --git a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivityLoader.java b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivityLoader.java
index 9251eba5d2..7683aa5f34 100644
--- a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivityLoader.java
+++ b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivityLoader.java
@@ -47,6 +47,7 @@ import android.os.Build;
import android.os.Bundle;
import android.view.Window;
+import org.qtproject.qt.android.QtNative;
import java.lang.reflect.Field;
@@ -113,9 +114,20 @@ public class QtActivityLoader extends QtLoader {
}
m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
+ if (QtNative.isStarted()) {
+ boolean updated = QtNative.activityDelegate().updateActivity(m_activity);
+ if (!updated) {
+ // could not update the activity so restart the application
+ Intent intent = Intent.makeRestartActivityTask(m_activity.getComponentName());
+ m_activity.startActivity(intent);
+ QtNative.quitApp();
+ Runtime.getRuntime().exit(0);
+ }
+
+ // there can only be a valid delegate object if the QtNative was started.
+ if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null)
+ QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
- if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
- QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
return;
}
@@ -124,15 +136,14 @@ public class QtActivityLoader extends QtLoader {
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
- if (null == m_activity.getLastNonConfigurationInstance()) {
- if (m_contextInfo.metaData.containsKey("android.app.background_running")
- && m_contextInfo.metaData.getBoolean("android.app.background_running")) {
- ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
- } else {
- ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
- }
-
- startApp(true);
+ if (m_contextInfo.metaData.containsKey("android.app.background_running")
+ && m_contextInfo.metaData.getBoolean("android.app.background_running")) {
+ ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
+ } else {
+ ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
}
+
+ startApp(true);
+
}
}
diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp
index b1bbd9c262..d94507b5a6 100644
--- a/src/corelib/kernel/qjnihelpers.cpp
+++ b/src/corelib/kernel/qjnihelpers.cpp
@@ -74,6 +74,8 @@ Q_GLOBAL_STATIC(QMutex, g_onBindListenerMutex);
Q_GLOBAL_STATIC(QSemaphore, g_waitForServiceSetupSemaphore);
Q_GLOBAL_STATIC(QAtomicInt, g_serviceSetupLockers);
+Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex);
+
namespace {
struct GenericMotionEventListeners {
QMutex mutex;
@@ -108,6 +110,41 @@ static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
return ret;
}
+static jboolean updateNativeActivity(JNIEnv *env, jclass = nullptr)
+{
+
+ jclass jQtNative = env->FindClass("org/qtproject/qt/android/QtNative");
+ if (QJniEnvironment::checkAndClearExceptions(env))
+ return JNI_FALSE;
+
+ jmethodID activityMethodID =
+ env->GetStaticMethodID(jQtNative, "activity", "()Landroid/app/Activity;");
+ if (QJniEnvironment::checkAndClearExceptions(env))
+ return JNI_FALSE;
+
+ jobject activity = env->CallStaticObjectMethod(jQtNative, activityMethodID);
+ if (QJniEnvironment::checkAndClearExceptions(env))
+ return JNI_FALSE;
+
+ QWriteLocker locker(g_updateMutex());
+
+ if (g_jActivity) {
+ env->DeleteGlobalRef(g_jActivity);
+ g_jActivity = nullptr;
+ }
+
+ if (activity) {
+ g_jActivity = env->NewGlobalRef(activity);
+ env->DeleteLocalRef(activity);
+ }
+
+ env->DeleteLocalRef(jQtNative);
+ if (QJniEnvironment::checkAndClearExceptions(env))
+ return JNI_FALSE;
+
+ return JNI_TRUE;
+}
+
namespace {
class ActivityResultListeners
{
@@ -271,6 +308,7 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
static const JNINativeMethod methods[] = {
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
+ {"updateNativeActivity", "()Z", reinterpret_cast<void *>(updateNativeActivity) },
};
const bool regOk = (env->RegisterNatives(jQtNative, methods, sizeof(methods) / sizeof(methods[0])) == JNI_OK);
@@ -289,6 +327,7 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
jobject QtAndroidPrivate::activity()
{
+ QReadLocker locker(g_updateMutex());
return g_jActivity;
}
@@ -299,12 +338,13 @@ jobject QtAndroidPrivate::service()
jobject QtAndroidPrivate::context()
{
+ QReadLocker locker(g_updateMutex());
if (g_jActivity)
return g_jActivity;
if (g_jService)
return g_jService;
- return 0;
+ return nullptr;
}
JavaVM *QtAndroidPrivate::javaVM()