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