From f7aadf60ded607d1be590e5ddcc75960a7908a27 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Fri, 25 Oct 2024 10:29:24 +0300 Subject: [PATCH] Android: make QtActivityDelegate practically NonNull MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid any potential null access on the delegate from various paths like onResume(), onPause(), etc. where the delegate or some of its member or delegates might also be null, make it final and assign it a default instance in the Activity constructor. This applies the same way for QtDisplayManager and QtInputDelegate, which are final and assigned an instance at the QtActivityDelegate constructor. This means also, that each of those delegates or classes should ensure any of objects used under them are safely called or not in null cases. This should help us reduce the amount of potential NullPointerExceptions. Fixes: QTBUG-129704 Change-Id: I36861c95d3b389fd88822aac0ae3616ccb3e304d Reviewed-by: Tinja Paavoseppä (cherry picked from commit 64f152d52a8720de71c7440ca2979345b919c58c) Reviewed-by: Qt Cherry-pick Bot --- .../qtproject/qt/android/QtActivityBase.java | 9 ++- .../qt/android/QtActivityDelegate.java | 81 ++++++++++++++----- .../qt/android/QtActivityDelegateBase.java | 22 +++-- .../qt/android/QtDisplayManager.java | 6 +- .../qtproject/qt/android/QtInputDelegate.java | 26 +++--- 5 files changed, 94 insertions(+), 50 deletions(-) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java index 0ae4c2e731a..4b680c1e174 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java @@ -32,7 +32,7 @@ public class QtActivityBase extends Activity private boolean m_retainNonConfigurationInstance = false; private Configuration m_prevConfig; - private QtActivityDelegate m_delegate; + private final QtActivityDelegate m_delegate; public static final String EXTRA_SOURCE_INFO = "org.qtproject.qt.android.sourceInfo"; @@ -83,6 +83,11 @@ public class QtActivityBase extends Activity Runtime.getRuntime().exit(0); } + public QtActivityBase() + { + m_delegate = new QtActivityDelegate(this); + } + @Override protected void onCreate(Bundle savedInstanceState) { @@ -102,8 +107,6 @@ public class QtActivityBase extends Activity restartApplication(); } - m_delegate = new QtActivityDelegate(this); - QtNative.registerAppStateListener(m_delegate); addReferrer(getIntent()); 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 fae6a3fbac1..cca26fb31e3 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -48,7 +48,12 @@ class QtActivityDelegate extends QtActivityDelegateBase QtActivityDelegate(Activity activity) { super(activity); + } + @Override + void initMembers() + { + super.initMembers(); setActionBarVisibility(false); setActivityBackgroundDrawable(); } @@ -82,10 +87,15 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override public void setSystemUiVisibility(int systemUiVisibility) { + if (m_layout == null) + return; + QtNative.runAction(() -> { - m_displayManager.setSystemUiVisibility(systemUiVisibility); - m_layout.requestLayout(); - QtNative.updateWindow(); + if (m_layout != null) { + m_displayManager.setSystemUiVisibility(systemUiVisibility); + m_layout.requestLayout(); + QtNative.updateWindow(); + } }); } @@ -101,12 +111,19 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override void startNativeApplicationImpl(String appParams, String mainLib) { + if (m_layout == null) { + Log.e(QtTAG, "Unable to start native application with a null layout"); + return; + } + m_layout.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - QtNative.startApplication(appParams, mainLib); - m_layout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + if (m_layout != null) { + QtNative.startApplication(appParams, mainLib); + m_layout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } } }); } @@ -114,9 +131,11 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override protected void setUpLayout() { - int orientation = m_activity.getResources().getConfiguration().orientation; + // This should be assigned only once, otherwise, we'd have to check + // for null everywhere including before and inside runAction() Runnables. m_layout = new QtRootLayout(m_activity); + int orientation = m_activity.getResources().getConfiguration().orientation; setUpSplashScreen(orientation); m_activity.registerForContextMenu(m_layout); m_activity.setContentView(m_layout, @@ -155,6 +174,11 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override protected void setUpSplashScreen(int orientation) { + if (m_layout == null) { + Log.e(QtTAG, "Unable to setup splash screen with a null layout"); + return; + } + try { ActivityInfo info = m_activity.getPackageManager().getActivityInfo( m_activity.getComponentName(), @@ -192,7 +216,7 @@ class QtActivityDelegate extends QtActivityDelegateBase if (m_splashScreen == null) return; - if (duration <= 0) { + if (m_layout != null && duration <= 0) { m_layout.removeView(m_splashScreen); m_splashScreen = null; return; @@ -303,7 +327,16 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override public void openContextMenu(final int x, final int y, final int w, final int h) { + if (m_layout == null) { + Log.e(QtTAG, "Unable to open context menu with a null layout"); + return; + } + m_layout.postDelayed(() -> { + if (m_layout == null) { + Log.w(QtTAG, "Unable to open context menu on null layout"); + return; + } final QtEditText focusedEditText = m_inputDelegate.getCurrentQtEditText(); if (focusedEditText == null) { Log.w(QtTAG, "No focused view when trying to open context menu"); @@ -350,10 +383,13 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override public void addTopLevelWindow(final QtWindow window) { - if (window == null) + if (m_layout == null || window == null) return; QtNative.runAction(()-> { + if (m_layout == null) + return; + if (m_topLevelWindows.size() == 0) { if (m_dummyView != null) { m_layout.removeView(m_dummyView); @@ -379,7 +415,7 @@ class QtActivityDelegate extends QtActivityDelegateBase // Keep last frame in stack until it is replaced to get correct // shutdown transition m_dummyView = window; - } else { + } else if (m_layout != null) { m_layout.removeView(window); } } @@ -390,22 +426,26 @@ class QtActivityDelegate extends QtActivityDelegateBase @Override public void bringChildToFront(final int id) { - QtNative.runAction(() -> { - QtWindow window = m_topLevelWindows.get(id); - if (window != null) - m_layout.moveChild(window, m_topLevelWindows.size() - 1); - }); + if (m_layout != null) { + QtNative.runAction(() -> { + QtWindow window = m_topLevelWindows.get(id); + if (window != null && m_layout != null) + m_layout.moveChild(window, m_topLevelWindows.size() - 1); + }); + } } @UsedFromNativeCode @Override public void bringChildToBack(int id) { - QtNative.runAction(() -> { - QtWindow window = m_topLevelWindows.get(id); - if (window != null) - m_layout.moveChild(window, 0); - }); + if (m_layout != null) { + QtNative.runAction(() -> { + QtWindow window = m_topLevelWindows.get(id); + if (window != null && m_layout != null) + m_layout.moveChild(window, 0); + }); + } } private void setActivityBackgroundDrawable() @@ -429,6 +469,9 @@ class QtActivityDelegate extends QtActivityDelegateBase @UsedFromNativeCode void insertNativeView(int id, View view, int x, int y, int w, int h) { + if (m_layout == null) + return; + QtNative.runAction(()-> { if (m_dummyView != null) { m_layout.removeView(m_dummyView); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java index c99695e6dde..bd5568fb93f 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java @@ -31,14 +31,16 @@ import android.view.WindowInsetsController; import android.widget.ImageView; import android.widget.PopupMenu; +import org.qtproject.qt.android.QtInputDelegate.KeyboardVisibilityListener; + import java.util.HashMap; abstract class QtActivityDelegateBase { - protected Activity m_activity; - protected HashMap m_topLevelWindows; - protected QtDisplayManager m_displayManager = null; - protected QtInputDelegate m_inputDelegate = null; + protected final Activity m_activity; + protected final HashMap m_topLevelWindows = new HashMap<>(); + protected final QtDisplayManager m_displayManager; + protected final QtInputDelegate m_inputDelegate; private boolean m_membersInitialized = false; private boolean m_contextMenuVisible = false; @@ -55,8 +57,9 @@ abstract class QtActivityDelegateBase QtActivityDelegateBase(Activity activity) { m_activity = activity; - // Set native context QtNative.setActivity(m_activity); + m_displayManager = new QtDisplayManager(m_activity); + m_inputDelegate = new QtInputDelegate(m_displayManager::updateFullScreen); } QtDisplayManager displayManager() { @@ -88,14 +91,9 @@ abstract class QtActivityDelegateBase void initMembers() { m_membersInitialized = true; - m_topLevelWindows = new HashMap(); - - m_displayManager = new QtDisplayManager(m_activity); + m_topLevelWindows.clear(); m_displayManager.registerDisplayListener(); - - QtInputDelegate.KeyboardVisibilityListener keyboardVisibilityListener = - () -> m_displayManager.updateFullScreen(); - m_inputDelegate = new QtInputDelegate(m_activity, keyboardVisibilityListener); + m_inputDelegate.initInputMethodManager(m_activity); try { PackageManager pm = m_activity.getPackageManager(); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java index e4de2434e22..6c8fbfd50bf 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java @@ -46,16 +46,12 @@ class QtDisplayManager { private static int m_previousRotation = -1; - private DisplayManager.DisplayListener m_displayListener = null; + private final DisplayManager.DisplayListener m_displayListener; private final Activity m_activity; QtDisplayManager(Activity activity) { m_activity = activity; - initDisplayListener(); - } - - private void initDisplayListener() { m_displayListener = new DisplayManager.DisplayListener() { @Override public void onDisplayAdded(int displayId) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index c1ebeeee47d..1fb25b5fe98 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -43,7 +43,7 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt // handle methods private QtEditText m_currentEditText = null; - private final InputMethodManager m_imm; + private InputMethodManager m_imm; private boolean m_keyboardIsVisible = false; private boolean m_isKeyboardHidingAnimationOngoing = false; @@ -72,9 +72,13 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt private final KeyboardVisibilityListener m_keyboardVisibilityListener; - QtInputDelegate(Activity activity, KeyboardVisibilityListener listener) + QtInputDelegate(KeyboardVisibilityListener listener) + { + m_keyboardVisibilityListener = listener; + } + + void initInputMethodManager(Activity activity) { - this.m_keyboardVisibilityListener = listener; m_imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); } @@ -84,10 +88,10 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt final int candidatesStart, final int candidatesEnd) { QtNative.runAction(() -> { - if (m_imm == null) - return; - - m_imm.updateSelection(m_currentEditText, selStart, selEnd, candidatesStart, candidatesEnd); + if (m_imm != null) { + m_imm.updateSelection(m_currentEditText, selStart, selEnd, + candidatesStart, candidatesEnd); + } }); } @@ -104,12 +108,11 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt return; m_currentEditText.setEditTextOptions(enterKeyType, inputHints); - m_currentEditText.setLayoutParams(new QtLayout.LayoutParams(width, height, x, y)); - m_currentEditText.requestFocus(); - m_currentEditText.postDelayed(() -> { + if (m_imm == null) + return; m_imm.showSoftInput(m_currentEditText, 0, new ResultReceiver(new Handler()) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -269,7 +272,8 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt if (!visibility) { // Hiding the keyboard clears the immersive mode, so we need to set it again. m_keyboardVisibilityListener.onKeyboardVisibilityChange(); - m_currentEditText.clearFocus(); + if (m_currentEditText != null) + m_currentEditText.clearFocus(); } }