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
QtNative.java
Go to the documentation of this file.
1// Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
2// Copyright (C) 2023 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
6package org.qtproject.qt.android;
7
8import android.app.Activity;
9import android.app.Service;
10import android.content.Context;
11import android.content.Intent;
12import android.content.UriPermission;
13import android.content.pm.PackageManager;
14import android.net.Uri;
15import android.os.Handler;
16import android.os.IBinder;
17import android.os.Looper;
18import android.system.Os;
19import android.util.Log;
20import android.view.ContextMenu;
21import android.view.Menu;
22import android.view.View;
23
24import java.lang.ref.WeakReference;
25import java.lang.UnsatisfiedLinkError;
26import java.security.KeyStore;
27import java.security.cert.X509Certificate;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.Objects;
31
32import javax.net.ssl.TrustManager;
33import javax.net.ssl.TrustManagerFactory;
34import javax.net.ssl.X509TrustManager;
35
36// ### Qt7: make private and find new API for onNewIntent()
37public class QtNative
38{
39 private static WeakReference<Activity> m_activity = null;
40 private static WeakReference<Service> m_service = null;
41 private static final Object m_mainActivityMutex = new Object(); // mutex used to synchronize runnable operations
42
43 private static final ApplicationStateDetails m_stateDetails = new ApplicationStateDetails();
44
45 static final String QtTAG = "Qt JAVA";
46
47 // a list of all actions which could not be performed (e.g. the main activity is destroyed, etc.)
48 private static final BackgroundActionsTracker m_backgroundActionsTracker = new BackgroundActionsTracker();
49 private static QtThread m_qtThread = null;
50 private static final Object m_qtThreadLock = new Object();
51 private static ClassLoader m_classLoader = null;
52
53 private static final Runnable runPendingCppRunnablesRunnable = QtNative::runPendingCppRunnables;
54 private static final ArrayList<AppStateDetailsListener> m_appStateListeners = new ArrayList<>();
55 private static final Object m_appStateListenersLock = new Object();
56
58 static ClassLoader classLoader()
59 {
60 return m_classLoader;
61 }
62
63 static void setClassLoader(ClassLoader classLoader)
64 {
65 m_classLoader = classLoader;
66 }
67
68 static void setActivity(Activity qtMainActivity)
69 {
70 synchronized (m_mainActivityMutex) {
71 m_activity = new WeakReference<>(qtMainActivity);
72 try {
73 if (m_stateDetails.isStarted)
74 updateNativeActivity();
75 } catch (UnsatisfiedLinkError ignored) {
76 // No-op - this happens in certain e.g. QtQuick for Android cases when we set the
77 // Activity for the first time, before Qt native libraries have been loaded. The
78 // C++ side will update its reference when the library is loaded.
79 }
80 }
81 }
82
83 static void setService(Service qtMainService)
84 {
85 synchronized (m_mainActivityMutex) {
86 m_service = new WeakReference<>(qtMainService);
87 }
88 }
89
91 static Activity activity()
92 {
93 synchronized (m_mainActivityMutex) {
94 return m_activity != null ? m_activity.get() : null;
95 }
96 }
97
98 static boolean isActivityValid()
99 {
100 return m_activity != null && m_activity.get() != null;
101 }
102
104 static Service service()
105 {
106 synchronized (m_mainActivityMutex) {
107 return m_service != null ? m_service.get() : null;
108 }
109 }
110
111 static boolean isServiceValid()
112 {
113 return m_service != null && m_service.get() != null;
114 }
115
117 static Context getContext() {
118 if (isActivityValid())
119 return m_activity.get();
120 return service();
121 }
122
124 static String[] getStringArray(String joinedString)
125 {
126 return joinedString.split(",");
127 }
128
129 private static String getCurrentMethodNameLog()
130 {
131 return new Exception().getStackTrace()[1].getMethodName() + ": ";
132 }
133
135 private static Uri getUriWithValidPermission(Context context, String uri, String openMode)
136 {
137 Uri parsedUri;
138 try {
139 parsedUri = Uri.parse(uri);
140 } catch (NullPointerException e) {
141 e.printStackTrace();
142 return null;
143 }
144
145 try {
146 String scheme = parsedUri.getScheme();
147
148 // We only want to check permissions for content Uris
149 if (scheme != null && scheme.compareTo("content") != 0)
150 return parsedUri;
151
152 List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
153 String uriStr = parsedUri.getPath();
154
155 for (int i = 0; i < permissions.size(); ++i) {
156 Uri iterUri = permissions.get(i).getUri();
157 boolean isRequestPermission = permissions.get(i).isReadPermission();
158
159 if (!openMode.equals("r"))
160 isRequestPermission = permissions.get(i).isWritePermission();
161
162 final boolean isSameAuthority =
163 Objects.equals(iterUri.getAuthority(), parsedUri.getAuthority());
164 final boolean isSamePath = Objects.equals(iterUri.getPath(), uriStr);
165
166 if (isSameAuthority && isSamePath && isRequestPermission) {
167 return iterUri;
168 }
169 }
170
171 // if we only have transient permissions on uri all the above will fail,
172 // but we will be able to read the file anyway, so continue with uri here anyway
173 // and check for SecurityExceptions later
174 return parsedUri;
175 } catch (SecurityException e) {
176 Log.e(QtTAG, getCurrentMethodNameLog() + e);
177 return parsedUri;
178 }
179 }
180
182 static boolean openURL(Context context, String url, String mime)
183 {
184 final Uri uri = getUriWithValidPermission(context, url, "r");
185 if (uri == null) {
186 Log.e(QtTAG, getCurrentMethodNameLog() + "received invalid/null Uri");
187 return false;
188 }
189
190 try {
191 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
192 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
193 if (!mime.isEmpty())
194 intent.setDataAndType(uri, mime);
195
196 Activity activity = activity();
197 if (activity == null) {
198 Log.w(QtTAG, "openURL(): The activity reference is null");
199 return false;
200 }
201
202 activity.startActivity(intent);
203
204 return true;
205 } catch (Exception e) {
206 Log.e(QtTAG, getCurrentMethodNameLog() + e);
207 return false;
208 }
209 }
210
211 static QtThread getQtThread() {
212 if (m_qtThread != null && m_qtThread.isAlive())
213 return m_qtThread;
214
215 synchronized (m_qtThreadLock) {
216 if (m_qtThread == null || !m_qtThread.isAlive())
217 m_qtThread = new QtThread();
218
219 return m_qtThread;
220 }
221 }
222
224 default void onAppStateDetailsChanged(ApplicationStateDetails details) {}
225 default void onNativePluginIntegrationReadyChanged(boolean ready) {}
226 }
227
228 // Keep in sync with src/corelib/global/qnamespace.h
229 static class ApplicationState {
230 static final int ApplicationSuspended = 0x0;
231 static final int ApplicationHidden = 0x1;
232 static final int ApplicationInactive = 0x2;
233 static final int ApplicationActive = 0x4;
234 }
235
236 static class ApplicationStateDetails {
237 int state = ApplicationState.ApplicationSuspended;
238 boolean nativePluginIntegrationReady = false;
239 boolean isStarted = false;
240 }
241
242 static ApplicationStateDetails getStateDetails()
243 {
244 return m_stateDetails;
245 }
246
247 static void setStarted(boolean started)
248 {
249 m_stateDetails.isStarted = started;
250 notifyAppStateDetailsChanged(m_stateDetails);
251 }
252
253 @UsedFromNativeCode
254 static void notifyNativePluginIntegrationReady(boolean ready)
255 {
256 m_stateDetails.nativePluginIntegrationReady = ready;
257 notifyNativePluginIntegrationReadyChanged(ready);
258 notifyAppStateDetailsChanged(m_stateDetails);
259
260 // Only set queue size when the plugin is fully loaded.
261 final String bufferSize = Os.getenv("QT_ANDROID_BACKGROUND_ACTIONS_QUEUE_SIZE");
262 if (bufferSize != null) {
263 try {
264 final int size = Integer.parseInt(bufferSize);
265 m_backgroundActionsTracker.setMaxAllowedActions(size);
266 } catch (NumberFormatException exception) {
267 Log.e(QtTAG, "Parsing failed, QT_ANDROID_BACKGROUND_ACTIONS_QUEUE_SIZE value is not an integer");
268 }
269 }
270 }
271
272 static void setApplicationState(int state)
273 {
274 synchronized (m_mainActivityMutex) {
275 m_stateDetails.state = state;
276 if (state == ApplicationState.ApplicationActive)
277 m_backgroundActionsTracker.processActions();
278 }
279 updateApplicationState(state);
280 notifyAppStateDetailsChanged(m_stateDetails);
281 }
282
283 static void registerAppStateListener(AppStateDetailsListener listener) {
284 synchronized (m_appStateListenersLock) {
285 if (!m_appStateListeners.contains(listener))
286 m_appStateListeners.add(listener);
287 }
288 }
289
290 static void unregisterAppStateListener(AppStateDetailsListener listener) {
291 synchronized (m_appStateListenersLock) {
292 m_appStateListeners.remove(listener);
293 }
294 }
295
296 static void notifyNativePluginIntegrationReadyChanged(boolean ready) {
297 synchronized (m_appStateListenersLock) {
298 for (final AppStateDetailsListener listener : m_appStateListeners)
299 listener.onNativePluginIntegrationReadyChanged(ready);
300 }
301 }
302
303 static void notifyAppStateDetailsChanged(ApplicationStateDetails details) {
304 synchronized (m_appStateListenersLock) {
305 for (AppStateDetailsListener listener : m_appStateListeners)
306 listener.onAppStateDetailsChanged(details);
307 }
308 }
309
310 // Post a runnable to Main (UI) Thread if the app is active,
311 // otherwise, queue it to be posted when the the app is active again
312 static void runAction(Runnable action)
313 {
314 runAction(action, true);
315 }
316
317 static void runAction(Runnable action, boolean queueWhenInactive)
318 {
319 synchronized (m_mainActivityMutex) {
320 final Looper mainLooper = Looper.getMainLooper();
321 final Handler handler = new Handler(mainLooper);
322
323 if (queueWhenInactive) {
324 final boolean isStateVisible =
325 (m_stateDetails.state != ApplicationState.ApplicationSuspended)
326 && (m_stateDetails.state != ApplicationState.ApplicationHidden);
327 final boolean active = (isActivityValid() && isStateVisible) || isServiceValid();
328 if (!active || !handler.post(action))
329 m_backgroundActionsTracker.enqueue(action);
330 } else {
331 handler.post(action);
332 }
333 }
334 }
335
336 @UsedFromNativeCode
337 private static void runPendingCppRunnablesOnAndroidThread()
338 {
339 synchronized (m_mainActivityMutex) {
340 if (isActivityValid()) {
341 if (m_stateDetails.state == ApplicationState.ApplicationActive)
342 m_activity.get().runOnUiThread(runPendingCppRunnablesRunnable);
343 else
344 runAction(runPendingCppRunnablesRunnable);
345 } else {
346 final Looper mainLooper = Looper.getMainLooper();
347 final Thread looperThread = mainLooper.getThread();
348 if (looperThread.equals(Thread.currentThread())) {
349 runPendingCppRunnablesRunnable.run();
350 } else {
351 final Handler handler = new Handler(mainLooper);
352 handler.post(runPendingCppRunnablesRunnable);
353 }
354 }
355 }
356 }
357
358 @UsedFromNativeCode
359 private static void setViewVisibility(final View view, final boolean visible)
360 {
361 runAction(() -> view.setVisibility(visible ? View.VISIBLE : View.GONE));
362 }
363
364 static void startApplication(String params, String mainLib)
365 {
366 if (m_stateDetails.isStarted)
367 return;
368
369 getQtThread().run(() -> initAndroidQpaPlugin());
370 final String qtParams = mainLib + " " + params;
371 getQtThread().post(() -> { startQtNativeApplication(qtParams); });
372 waitForServiceSetup();
373 setStarted(true);
374 }
375
376 @UsedFromNativeCode
377 static int checkSelfPermission(String permission)
378 {
379 synchronized (m_mainActivityMutex) {
380 Context context = getContext();
381 PackageManager pm = context.getPackageManager();
382 return pm.checkPermission(permission, context.getPackageName());
383 }
384 }
385
386 @UsedFromNativeCode
387 private static byte[][] getSSLCertificates()
388 {
389 ArrayList<byte[]> certificateList = new ArrayList<>();
390
391 try {
392 TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
393 factory.init((KeyStore) null);
394
395 for (TrustManager manager : factory.getTrustManagers()) {
396 if (manager instanceof X509TrustManager) {
397 X509TrustManager trustManager = (X509TrustManager) manager;
398
399 for (X509Certificate certificate : trustManager.getAcceptedIssuers()) {
400 byte[] buffer = certificate.getEncoded();
401 certificateList.add(buffer);
402 }
403 }
404 }
405 } catch (Exception e) {
406 Log.e(QtTAG, "Failed to get certificates", e);
407 }
408
409 byte[][] certificateArray = new byte[certificateList.size()][];
410 certificateArray = certificateList.toArray(certificateArray);
411 return certificateArray;
412 }
413
414 @UsedFromNativeCode
415 private static String[] listAssetContent(android.content.res.AssetManager asset, String path) {
416 String [] list;
417 ArrayList<String> res = new ArrayList<>();
418 try {
419 list = asset.list(path);
420 if (list != null) {
421 for (String file : list) {
422 try {
423 String[] isDir = asset.list(!path.isEmpty() ? path + "/" + file : file);
424 if (isDir != null && isDir.length > 0)
425 file += "/";
426 res.add(file);
427 } catch (Exception e) {
428 e.printStackTrace();
429 }
430 }
431 }
432 } catch (Exception e) {
433 e.printStackTrace();
434 }
435 return res.toArray(new String[0]);
436 }
437
438 // application methods
439 static native boolean initAndroidQpaPlugin();
440 static native void startQtNativeApplication(String params);
441 static native void waitForServiceSetup();
442 static native void terminateQtNativeApplication();
443 static native boolean updateNativeActivity();
444 // application methods
445
446 // application methods
447 static native void updateApplicationState(int state);
448 static native void updateLocale();
449
450 // menu methods
451 static native boolean onPrepareOptionsMenu(Menu menu);
452 static native boolean onOptionsItemSelected(int itemId, boolean checked);
453 static native void onOptionsMenuClosed(Menu menu);
454
455 static native void onCreateContextMenu(ContextMenu menu);
456 static native void fillContextMenu(Menu menu);
457 static native boolean onContextItemSelected(int itemId, boolean checked);
458 static native void onContextMenuClosed(Menu menu);
459 // menu methods
460
461 // activity methods
462 static native void onActivityResult(int requestCode, int resultCode, Intent data);
463 public static native void onNewIntent(Intent data);
464
465 static native void runPendingCppRunnables();
466
467 static native void sendRequestPermissionsResult(int requestCode, int[] grantResults);
468 // activity methods
469
470 // service methods
471 static native IBinder onBind(Intent intent);
472 // service methods
473}
PeripheralState state
static native void onNewIntent(Intent data)
QPainter Context
default void onNativePluginIntegrationReadyChanged(boolean ready)
default void onAppStateDetailsChanged(ApplicationStateDetails details)
static const QString context()
Definition java.cpp:396
[vector_of_multirole_objects_0]
Definition main.cpp:188
Catch::Generators::GeneratorWrapper< T > handler(Catch::Generators::GeneratorWrapper< T > &&generator)
Returns a generator wrapping generator that ensures that changes its semantics so that the first call...
GLvoid ** params
GLenum GLuint GLintptr GLsizeiptr size
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
[0]
GLuint res
GLsizei const GLchar *const * path
@ Handler
EGLContext EGLenum EGLClientBuffer buffer
QList< int > list
[14]
QItemEditorFactory * factory
QMenu menu
[5]
QNetworkAccessManager manager
[0]
QUrl url("http://my.server.tld")
QQuickView * view
[0]