From 5fd6704091febcc4abbc8d7ce06a393572524fa5 Mon Sep 17 00:00:00 2001 From: Samuel Mira Date: Wed, 20 Apr 2022 16:08:36 +0300 Subject: [PATCH] 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 Reviewed-by: Assam Boudjelthia --- .../qt/android/QtActivityDelegate.java | 168 +++++++++++------- .../org/qtproject/qt/android/QtNative.java | 12 +- .../qt/android/bindings/QtActivityLoader.java | 33 ++-- src/corelib/kernel/qjnihelpers.cpp | 42 ++++- 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 c6ae62273ec..24098de241c 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 eb398a70614..e750159d8ce 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 9251eba5d23..7683aa5f346 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 b1bbd9c2629..d94507b5a66 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(dispatchGenericMotionEvent)}, {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast(dispatchKeyEvent)}, + {"updateNativeActivity", "()Z", reinterpret_cast(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()