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 if (Objects.equals(iterUri.getPath(), uriStr) && isRequestPermission)
163 return iterUri;
164 }
165
166 // if we only have transient permissions on uri all the above will fail,
167 // but we will be able to read the file anyway, so continue with uri here anyway
168 // and check for SecurityExceptions later
169 return parsedUri;
170 } catch (SecurityException e) {
171 Log.e(QtTAG, getCurrentMethodNameLog() + e);
172 return parsedUri;
173 }
174 }
175
177 static boolean openURL(Context context, String url, String mime)
178 {
179 final Uri uri = getUriWithValidPermission(context, url, "r");
180 if (uri == null) {
181 Log.e(QtTAG, getCurrentMethodNameLog() + "received invalid/null Uri");
182 return false;
183 }
184
185 try {
186 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
187 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
188 if (!mime.isEmpty())
189 intent.setDataAndType(uri, mime);
190
191 Activity activity = activity();
192 if (activity == null) {
193 Log.w(QtTAG, "openURL(): The activity reference is null");
194 return false;
195 }
196
197 activity.startActivity(intent);
198
199 return true;
200 } catch (Exception e) {
201 Log.e(QtTAG, getCurrentMethodNameLog() + e);
202 return false;
203 }
204 }
205
206 static QtThread getQtThread() {
207 if (m_qtThread != null && m_qtThread.isAlive())
208 return m_qtThread;
209
210 synchronized (m_qtThreadLock) {
211 if (m_qtThread == null || !m_qtThread.isAlive())
212 m_qtThread = new QtThread();
213
214 return m_qtThread;
215 }
216 }
217
219 default void onAppStateDetailsChanged(ApplicationStateDetails details) {}
220 default void onNativePluginIntegrationReadyChanged(boolean ready) {}
221 }
222
223 // Keep in sync with src/corelib/global/qnamespace.h
224 static class ApplicationState {
225 static final int ApplicationSuspended = 0x0;
226 static final int ApplicationHidden = 0x1;
227 static final int ApplicationInactive = 0x2;
228 static final int ApplicationActive = 0x4;
229 }
230
231 static class ApplicationStateDetails {
232 int state = ApplicationState.ApplicationSuspended;
233 boolean nativePluginIntegrationReady = false;
234 boolean isStarted = false;
235 }
236
237 static ApplicationStateDetails getStateDetails()
238 {
239 return m_stateDetails;
240 }
241
242 static void setStarted(boolean started)
243 {
244 m_stateDetails.isStarted = started;
245 notifyAppStateDetailsChanged(m_stateDetails);
246 }
247
248 @UsedFromNativeCode
249 static void notifyNativePluginIntegrationReady(boolean ready)
250 {
251 m_stateDetails.nativePluginIntegrationReady = ready;
252 notifyNativePluginIntegrationReadyChanged(ready);
253 notifyAppStateDetailsChanged(m_stateDetails);
254
255 // Only set queue size when the plugin is fully loaded.
256 final String bufferSize = Os.getenv("QT_ANDROID_BACKGROUND_ACTIONS_QUEUE_SIZE");
257 if (bufferSize != null) {
258 try {
259 final int size = Integer.parseInt(bufferSize);
260 m_backgroundActionsTracker.setMaxAllowedActions(size);
261 } catch (NumberFormatException exception) {
262 Log.e(QtTAG, "Parsing failed, QT_ANDROID_BACKGROUND_ACTIONS_QUEUE_SIZE value is not an integer");
263 }
264 }
265 }
266
267 static void setApplicationState(int state)
268 {
269 synchronized (m_mainActivityMutex) {
270 m_stateDetails.state = state;
271 if (state == ApplicationState.ApplicationActive)
272 m_backgroundActionsTracker.processActions();
273 }
274 updateApplicationState(state);
275 notifyAppStateDetailsChanged(m_stateDetails);
276 }
277
278 static void registerAppStateListener(AppStateDetailsListener listener) {
279 synchronized (m_appStateListenersLock) {
280 if (!m_appStateListeners.contains(listener))
281 m_appStateListeners.add(listener);
282 }
283 }
284
285 static void unregisterAppStateListener(AppStateDetailsListener listener) {
286 synchronized (m_appStateListenersLock) {
287 m_appStateListeners.remove(listener);
288 }
289 }
290
291 static void notifyNativePluginIntegrationReadyChanged(boolean ready) {
292 synchronized (m_appStateListenersLock) {
293 for (final AppStateDetailsListener listener : m_appStateListeners)
294 listener.onNativePluginIntegrationReadyChanged(ready);
295 }
296 }
297
298 static void notifyAppStateDetailsChanged(ApplicationStateDetails details) {
299 synchronized (m_appStateListenersLock) {
300 for (AppStateDetailsListener listener : m_appStateListeners)
301 listener.onAppStateDetailsChanged(details);
302 }
303 }
304
305 // Post a runnable to Main (UI) Thread if the app is active,
306 // otherwise, queue it to be posted when the the app is active again
307 static void runAction(Runnable action)
308 {
309 runAction(action, true);
310 }
311
312 static void runAction(Runnable action, boolean queueWhenInactive)
313 {
314 synchronized (m_mainActivityMutex) {
315 final Looper mainLooper = Looper.getMainLooper();
316 final Handler handler = new Handler(mainLooper);
317
318 if (queueWhenInactive) {
319 final boolean isStateVisible =
320 (m_stateDetails.state != ApplicationState.ApplicationSuspended)
321 && (m_stateDetails.state != ApplicationState.ApplicationHidden);
322 final boolean active = (isActivityValid() && isStateVisible) || isServiceValid();
323 if (!active || !handler.post(action))
324 m_backgroundActionsTracker.enqueue(action);
325 } else {
326 handler.post(action);
327 }
328 }
329 }
330
331 @UsedFromNativeCode
332 private static void runPendingCppRunnablesOnAndroidThread()
333 {
334 synchronized (m_mainActivityMutex) {
335 if (isActivityValid()) {
336 if (m_stateDetails.state == ApplicationState.ApplicationActive)
337 m_activity.get().runOnUiThread(runPendingCppRunnablesRunnable);
338 else
339 runAction(runPendingCppRunnablesRunnable);
340 } else {
341 final Looper mainLooper = Looper.getMainLooper();
342 final Thread looperThread = mainLooper.getThread();
343 if (looperThread.equals(Thread.currentThread())) {
344 runPendingCppRunnablesRunnable.run();
345 } else {
346 final Handler handler = new Handler(mainLooper);
347 handler.post(runPendingCppRunnablesRunnable);
348 }
349 }
350 }
351 }
352
353 @UsedFromNativeCode
354 private static void setViewVisibility(final View view, final boolean visible)
355 {
356 runAction(() -> view.setVisibility(visible ? View.VISIBLE : View.GONE));
357 }
358
359 static void startApplication(String params, String mainLib)
360 {
361 if (m_stateDetails.isStarted)
362 return;
363
364 final String qtParams = mainLib + " " + params;
365 getQtThread().post(() -> { startQtNativeApplication(qtParams); });
366 }
367
368 @UsedFromNativeCode
369 static int checkSelfPermission(String permission)
370 {
371 synchronized (m_mainActivityMutex) {
372 Context context = getContext();
373 PackageManager pm = context.getPackageManager();
374 return pm.checkPermission(permission, context.getPackageName());
375 }
376 }
377
378 @UsedFromNativeCode
379 private static byte[][] getSSLCertificates()
380 {
381 ArrayList<byte[]> certificateList = new ArrayList<>();
382
383 try {
384 TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
385 factory.init((KeyStore) null);
386
387 for (TrustManager manager : factory.getTrustManagers()) {
388 if (manager instanceof X509TrustManager) {
389 X509TrustManager trustManager = (X509TrustManager) manager;
390
391 for (X509Certificate certificate : trustManager.getAcceptedIssuers()) {
392 byte[] buffer = certificate.getEncoded();
393 certificateList.add(buffer);
394 }
395 }
396 }
397 } catch (Exception e) {
398 Log.e(QtTAG, "Failed to get certificates", e);
399 }
400
401 byte[][] certificateArray = new byte[certificateList.size()][];
402 certificateArray = certificateList.toArray(certificateArray);
403 return certificateArray;
404 }
405
406 @UsedFromNativeCode
407 private static String[] listAssetContent(android.content.res.AssetManager asset, String path) {
408 String [] list;
409 ArrayList<String> res = new ArrayList<>();
410 try {
411 list = asset.list(path);
412 if (list != null) {
413 for (String file : list) {
414 try {
415 String[] isDir = asset.list(!path.isEmpty() ? path + "/" + file : file);
416 if (isDir != null && isDir.length > 0)
417 file += "/";
418 res.add(file);
419 } catch (Exception e) {
420 e.printStackTrace();
421 }
422 }
423 }
424 } catch (Exception e) {
425 e.printStackTrace();
426 }
427 return res.toArray(new String[0]);
428 }
429
430 // application methods
431 static native void startQtNativeApplication(String params);
432 static native void terminateQtNativeApplication();
433 static native boolean updateNativeActivity();
434 // application methods
435
436 // application methods
437 static native void updateApplicationState(int state);
438 static native void updateLocale();
439
440 // menu methods
441 static native boolean onPrepareOptionsMenu(Menu menu);
442 static native boolean onOptionsItemSelected(int itemId, boolean checked);
443 static native void onOptionsMenuClosed(Menu menu);
444
445 static native void onCreateContextMenu(ContextMenu menu);
446 static native void fillContextMenu(Menu menu);
447 static native boolean onContextItemSelected(int itemId, boolean checked);
448 static native void onContextMenuClosed(Menu menu);
449 // menu methods
450
451 // activity methods
452 static native void onActivityResult(int requestCode, int resultCode, Intent data);
453 public static native void onNewIntent(Intent data);
454
455 static native void runPendingCppRunnables();
456
457 static native void sendRequestPermissionsResult(int requestCode, int[] grantResults);
458 // activity methods
459
460 // service methods
461 static native IBinder onBind(Intent intent);
462 // service methods
463}
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:398
[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...
GLenum GLuint GLintptr GLsizeiptr size
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
[0]
void ** params
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]