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()