Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtActivityDelegate.java
Go to the documentation of this file.
1// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
2// Copyright (C) 2023 The Qt Company Ltd.
3// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6package org.qtproject.qt.android;
7
8import android.app.Activity;
9import android.content.pm.ActivityInfo;
10import android.content.pm.PackageManager;
11import android.content.res.Configuration;
12import android.graphics.drawable.ColorDrawable;
13import android.graphics.drawable.Drawable;
14import android.graphics.Rect;
15import android.util.DisplayMetrics;
16import android.util.Log;
17import android.util.TypedValue;
18import android.view.ViewTreeObserver;
19import android.os.Build;
20import android.view.WindowInsets;
21import android.view.WindowInsetsAnimation;
22import android.view.animation.AccelerateInterpolator;
23import android.view.animation.AlphaAnimation;
24import android.view.animation.Animation;
25import android.view.Menu;
26import android.view.View;
27import android.view.ViewConfiguration;
28import android.view.ViewGroup;
29import android.widget.ImageView;
30import android.widget.PopupMenu;
31
32import java.util.HashMap;
33
34class QtActivityDelegate extends QtActivityDelegateBase
36 QtNative.AppStateDetailsListener
37{
38 private static final String QtTAG = "QtActivityDelegate";
39
40 private QtRootLayout m_layout = null;
41 private ImageView m_splashScreen = null;
42 private boolean m_splashScreenSticky = false;
43 private boolean m_backendsRegistered = false;
44
45 private View m_dummyView = null;
46
47
48 QtActivityDelegate(Activity activity)
49 {
50 super(activity);
51 }
52
54 void initMembers()
55 {
56 super.initMembers();
57 setActionBarVisibility(false);
58 setActivityBackgroundDrawable();
59 if (QtNativeAccessibility.accessibilitySupported())
60 m_accessibilityDelegate.initLayoutAccessibility(m_layout);
61 }
62
63 void registerBackends()
64 {
65 if (m_backendsRegistered || BackendRegister.isNull())
66 return;
67
68 m_backendsRegistered = true;
69 BackendRegister.registerBackend(QtWindowInterface.class, QtActivityDelegate.this);
70 BackendRegister.registerBackend(QtAccessibilityInterface.class, QtActivityDelegate.this);
71 BackendRegister.registerBackend(QtMenuInterface.class, QtActivityDelegate.this);
72 BackendRegister.registerBackend(QtInputInterface.class, m_inputDelegate);
73 }
74
75 void unregisterBackends()
76 {
77 if (!m_backendsRegistered)
78 return;
79
80 m_backendsRegistered = false;
81
82 if (BackendRegister.isNull())
83 return;
84
85 BackendRegister.unregisterBackend(QtWindowInterface.class);
86 BackendRegister.unregisterBackend(QtAccessibilityInterface.class);
87 BackendRegister.unregisterBackend(QtMenuInterface.class);
88 BackendRegister.unregisterBackend(QtInputInterface.class);
89 }
90
92 final public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) {
93 if (details.isStarted)
94 registerBackends();
95 else
96 unregisterBackends();
97 }
98
100 void startNativeApplicationImpl(String appParams, String mainLib)
101 {
102 if (m_layout == null) {
103 Log.e(QtTAG, "Unable to start native application with a null layout");
104 return;
105 }
106
107 m_layout.getViewTreeObserver().addOnGlobalLayoutListener(
108 new ViewTreeObserver.OnGlobalLayoutListener() {
109 @Override
110 public void onGlobalLayout() {
111 if (m_layout != null) {
112 QtNative.startApplication(appParams, mainLib);
113 m_layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
114 }
115 }
116 });
117 }
118
119 @Override
120 protected void setUpLayout()
121 {
122 // This should be assigned only once, otherwise, we'd have to check
123 // for null everywhere including before and inside runAction() Runnables.
124 m_layout = new QtRootLayout(m_activity);
125
126 int orientation = m_activity.getResources().getConfiguration().orientation;
127 setUpSplashScreen(orientation);
128 m_activity.registerForContextMenu(m_layout);
129 ViewGroup.LayoutParams rootParams = new ViewGroup.LayoutParams(
130 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
131 m_activity.setContentView(m_layout, rootParams);
132
133 handleUiModeChange();
134
135 m_displayManager.initDisplayProperties();
136
137 m_layout.getViewTreeObserver().addOnPreDrawListener(() -> {
138 if (!m_inputDelegate.isKeyboardVisible())
139 return true;
140
141 Rect r = new Rect();
142 m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
143 DisplayMetrics metrics = new DisplayMetrics();
144 QtDisplayManager.getDisplay(m_activity).getMetrics(metrics);
145 final int kbHeight = metrics.heightPixels - r.bottom;
146 if (kbHeight < 0) {
147 m_inputDelegate.setKeyboardVisibility(false, System.nanoTime());
148 return true;
149 }
150 final int[] location = new int[2];
151 m_layout.getLocationOnScreen(location);
152 QtInputDelegate.keyboardGeometryChanged(location[0], r.bottom - location[1],
153 r.width(), kbHeight);
154 return true;
155 });
156 }
157
158 @Override
159 protected void setUpSplashScreen(int orientation)
160 {
161 if (m_layout == null) {
162 Log.e(QtTAG, "Unable to setup splash screen with a null layout");
163 return;
164 }
165
166 try {
167 ActivityInfo info = m_activity.getPackageManager().getActivityInfo(
168 m_activity.getComponentName(),
169 PackageManager.GET_META_DATA);
170
171 String splashScreenKey = "android.app.splash_screen_drawable_"
172 + (orientation == Configuration.ORIENTATION_LANDSCAPE ? "landscape" : "portrait");
173 if (!info.metaData.containsKey(splashScreenKey))
174 splashScreenKey = "android.app.splash_screen_drawable";
175
176 if (info.metaData.containsKey(splashScreenKey)) {
177 m_splashScreenSticky =
178 info.metaData.containsKey("android.app.splash_screen_sticky") &&
179 info.metaData.getBoolean("android.app.splash_screen_sticky");
180
181 int id = info.metaData.getInt(splashScreenKey);
182 m_splashScreen = new ImageView(m_activity);
183 m_splashScreen.setImageDrawable(m_activity.getResources().getDrawable(
184 id, m_activity.getTheme()));
185 m_splashScreen.setScaleType(ImageView.ScaleType.FIT_XY);
186 m_splashScreen.setLayoutParams(new ViewGroup.LayoutParams(
187 ViewGroup.LayoutParams.MATCH_PARENT,
188 ViewGroup.LayoutParams.MATCH_PARENT));
189 m_layout.addView(m_splashScreen);
190 }
191 } catch (Exception e) {
192 e.printStackTrace();
193 }
194 }
195
196 @Override
197 protected void hideSplashScreen(final int duration)
198 {
199 QtNative.runAction(() -> {
200 if (m_splashScreen == null)
201 return;
202
203 if (m_layout != null && duration <= 0) {
204 m_layout.removeView(m_splashScreen);
205 m_splashScreen = null;
206 return;
207 }
208
209 final Animation fadeOut = new AlphaAnimation(1, 0);
210 fadeOut.setInterpolator(new AccelerateInterpolator());
211 fadeOut.setDuration(duration);
212
213 fadeOut.setAnimationListener(new Animation.AnimationListener() {
214 @Override
215 public void onAnimationEnd(Animation animation) {
216 hideSplashScreen(0);
217 }
218
219 @Override
220 public void onAnimationRepeat(Animation animation) {
221 }
222
223 @Override
224 public void onAnimationStart(Animation animation) {
225 }
226 });
227
228 m_splashScreen.startAnimation(fadeOut);
229 });
230 }
231
232 @Override
233 public void notifyLocationChange(int viewId)
234 {
235 m_accessibilityDelegate.notifyLocationChange(viewId);
236 }
237
238 @Override
239 public void notifyObjectHide(int viewId, int parentId)
240 {
241 m_accessibilityDelegate.notifyObjectHide(viewId, parentId);
242 }
243
244 @Override
245 public void notifyObjectShow(int parentId)
246 {
247 m_accessibilityDelegate.notifyObjectShow(parentId);
248 }
249
250 @Override
251 public void notifyObjectFocus(int viewId)
252 {
253 m_accessibilityDelegate.notifyObjectFocus(viewId);
254 }
255
256 @Override
257 public void notifyObjectDestroyed(int viewId, int parentId)
258 {
259 m_accessibilityDelegate.notifyObjectDestroyed(viewId, parentId);
260 }
261
262 @Override
263 public void notifyObjectCreated(int parentId)
264 {
265 m_accessibilityDelegate.notifyObjectCreated(parentId);
266 }
267
268 @Override
269 public void notifyValueChanged(int viewId)
270 {
271 m_accessibilityDelegate.notifyValueChanged(viewId);
272 }
273
274 @Override
275 public void notifyDescriptionOrNameChanged(int viewId)
276 {
277 m_accessibilityDelegate.notifyDescriptionOrNameChanged(viewId);
278 }
279
280 @Override
281 public void notifyScrolledEvent(int viewId)
282 {
283 m_accessibilityDelegate.notifyScrolledEvent(viewId);
284 }
285
286 @Override
287 public void notifyAnnouncementEvent(int viewId, String message)
288 {
289 m_accessibilityDelegate.notifyAnnouncementEvent(viewId, message);
290 }
291
292 @Override
293 public void notifyTextChanged(int viewId, String text, String beforeText,
294 int fromIndex, int addedCount, int removedCount)
295 {
296 m_accessibilityDelegate.notifyTextChanged(viewId, text, beforeText,
297 fromIndex, addedCount, removedCount);
298 }
299
300 // QtMenuInterface implementation begin
301 @Override
302 public void resetOptionsMenu()
303 {
304 QtNative.runAction(m_activity::invalidateOptionsMenu);
305 }
306
307 @Override
308 public void openOptionsMenu()
309 {
310 QtNative.runAction(m_activity::openOptionsMenu);
311 }
312
313 @Override
314 public void closeContextMenu()
315 {
316 QtNative.runAction(m_activity::closeContextMenu);
317 }
318
319 @Override
320 public void openContextMenu(final int x, final int y, final int w, final int h)
321 {
322 if (m_layout == null) {
323 Log.e(QtTAG, "Unable to open context menu with a null layout");
324 return;
325 }
326
327 m_layout.postDelayed(() -> {
328 if (m_layout == null) {
329 Log.w(QtTAG, "Unable to open context menu on null layout");
330 return;
331 }
332 final QtEditText focusedEditText = m_inputDelegate.getCurrentQtEditText();
333 if (focusedEditText == null) {
334 Log.w(QtTAG, "No focused view when trying to open context menu");
335 return;
336 }
337 m_layout.setLayoutParams(focusedEditText, new QtLayout.LayoutParams(w, h, x, y), false);
338 focusedEditText.requestLayout();
339 focusedEditText.getViewTreeObserver().addOnGlobalLayoutListener(
340 new ViewTreeObserver.OnGlobalLayoutListener() {
341 @Override
342 public void onGlobalLayout() {
343 focusedEditText.getViewTreeObserver().removeOnGlobalLayoutListener(this);
344 PopupMenu popup = new PopupMenu(m_activity, focusedEditText);
345 QtActivityDelegate.this.onCreatePopupMenu(popup.getMenu());
346 popup.setOnMenuItemClickListener(m_activity::onContextItemSelected);
347 popup.setOnDismissListener(popupMenu ->
348 m_activity.onContextMenuClosed(popupMenu.getMenu()));
349 popup.show();
350 }
351 });
352 }, 100);
353 }
354 // QtMenuInterface implementation end
355
356 void onCreatePopupMenu(Menu menu)
357 {
358 QtNative.fillContextMenu(menu);
359 setContextMenuVisible(true);
360 }
361
362 @Override
363 void setActionBarVisibility(boolean visible)
364 {
365 if (m_activity.getActionBar() == null)
366 return;
367 if (ViewConfiguration.get(m_activity).hasPermanentMenuKey() || !visible)
368 m_activity.getActionBar().hide();
369 else
370 m_activity.getActionBar().show();
371 }
372
373 private void fanOutSafeAreaMargins(WindowInsets insets)
374 {
375 for (QtWindow w : m_topLevelWindows.values())
376 w.reportSafeAreaMarginsFromRoot(insets);
377 }
378
379 private void registerDecorInsetsListener()
380 {
381 // Only the decor view sees every insets change, and showing or hiding
382 // the bars only animates, so forward both to the Qt windows.
383 final View decor = m_activity.getWindow().getDecorView();
384 decor.setOnApplyWindowInsetsListener((view, insets) -> {
385 fanOutSafeAreaMargins(insets);
386 return view.onApplyWindowInsets(insets);
387 });
388 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
389 decor.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(
390 WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
391 private WindowInsets m_lastAnimatedInsets;
392
393 @Override
394 public WindowInsets onProgress(WindowInsets insets,
395 java.util.List<WindowInsetsAnimation> animations)
396 {
397 m_lastAnimatedInsets = insets;
398 return insets;
399 }
400
401 @Override
402 public void onEnd(WindowInsetsAnimation animation)
403 {
404 // The root insets can still be stale here, the last
405 // animated insets hold the end state.
406 WindowInsets insets = m_lastAnimatedInsets != null
407 ? m_lastAnimatedInsets : decor.getRootWindowInsets();
408 if (insets != null)
409 fanOutSafeAreaMargins(insets);
410 }
411 });
412 }
413 }
414
415 @UsedFromNativeCode
416 @Override
417 public void addTopLevelWindow(final QtWindow window)
418 {
419 if (m_layout == null || window == null)
420 return;
421
422 QtNative.runAction(()-> {
423 if (m_layout == null)
424 return;
425
426 if (m_topLevelWindows.isEmpty()) {
427 if (m_dummyView != null) {
428 m_layout.removeView(m_dummyView);
429 m_dummyView = null;
430 }
431 }
432
433 m_layout.addView(window, m_topLevelWindows.size());
434 if (m_topLevelWindows.isEmpty())
435 registerDecorInsetsListener();
436 m_topLevelWindows.put(window.getId(), window);
437 if (!m_splashScreenSticky)
438 hideSplashScreen();
439 });
440 }
441
442 @UsedFromNativeCode
443 @Override
444 public void removeTopLevelWindow(final int id)
445 {
446 QtNative.runAction(()-> {
447 if (m_topLevelWindows.containsKey(id)) {
448 QtWindow window = m_topLevelWindows.remove(id);
449 window.setOnApplyWindowInsetsListener(null); // Set in QtWindow for safe margins
450 if (m_topLevelWindows.isEmpty()) {
451 // Keep last frame in stack until it is replaced to get correct
452 // shutdown transition
453 m_dummyView = window;
454 } else if (m_layout != null) {
455 m_layout.removeView(window);
456 }
457 }
458 });
459 }
460
461 @UsedFromNativeCode
462 @Override
463 public void bringChildToFront(final int id)
464 {
465 if (m_layout != null) {
466 QtNative.runAction(() -> {
467 QtWindow window = m_topLevelWindows.get(id);
468 if (window != null && m_layout != null)
469 m_layout.moveChild(window, m_topLevelWindows.size() - 1);
470 });
471 }
472 }
473
474 @UsedFromNativeCode
475 @Override
476 public void bringChildToBack(int id)
477 {
478 if (m_layout != null) {
479 QtNative.runAction(() -> {
480 QtWindow window = m_topLevelWindows.get(id);
481 if (window != null && m_layout != null)
482 m_layout.moveChild(window, 0);
483 });
484 }
485 }
486
487 private void setActivityBackgroundDrawable()
488 {
489 TypedValue attr = new TypedValue();
490 m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground,
491 attr, true);
492 Drawable backgroundDrawable;
493 if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
494 attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
495 backgroundDrawable = new ColorDrawable(attr.data);
496 } else {
497 backgroundDrawable = m_activity.getResources().
498 getDrawable(attr.resourceId, m_activity.getTheme());
499 }
500
501 m_activity.getWindow().setBackgroundDrawable(backgroundDrawable);
502 }
503}
Napi::String String
Definition qnapi_p.h:70
void notifyDescriptionOrNameChanged(uint accessibilityObjectId)
void notifyObjectShow(uint accessibilityObjectId)
void notifyLocationChange(uint accessibilityObjectId)
void notifyObjectFocus(uint accessibilityObjectId)
void notifyObjectDestroyed(uint accessibilityObjectId)
void notifyObjectCreated(uint accessibilityObjectId)
void notifyObjectHide(uint accessibilityObjectId)
void notifyValueChanged(uint accessibilityObjectId)
void notifyAnnouncementEvent(uint accessibilityObjectId, const QString &message)
void notifyScrolledEvent(uint accessiblityObjectId)
self window
[2]
GLuint GLsizei const GLchar * message
GLint GLint GLint GLint GLint x
GLfloat GLfloat GLfloat w
[0]
GLboolean r
GLsizei GLenum const void GLuint GLsizei GLfloat * metrics
GLint location
GLint y
GLfloat GLfloat GLfloat GLfloat h
QMenu menu
[5]
QHostInfo info
[0]
QQuickView * view
[0]