Android: make QtActivityDelegate practically NonNull

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ä <tinja.paavoseppa@qt.io>
(cherry picked from commit 64f152d52a8720de71c7440ca2979345b919c58c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Assam Boudjelthia 2024-10-25 10:29:24 +03:00 committed by Qt Cherry-pick Bot
parent 7338e481a9
commit f7aadf60de
5 changed files with 94 additions and 50 deletions

View File

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

View File

@ -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);

View File

@ -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<Integer, QtWindow> m_topLevelWindows;
protected QtDisplayManager m_displayManager = null;
protected QtInputDelegate m_inputDelegate = null;
protected final Activity m_activity;
protected final HashMap<Integer, QtWindow> 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<Integer, QtWindow>();
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();

View File

@ -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) {

View File

@ -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();
}
}