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
QtScreenCaptureService.java
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4package org.qtproject.qt.android.multimedia;
5
6import android.app.Activity;
7import android.app.Notification;
8import android.app.NotificationChannel;
9import android.app.NotificationManager;
10import android.app.Service;
11import android.content.Intent;
12import android.hardware.display.DisplayManager;
13import android.hardware.display.VirtualDisplay;
14import android.graphics.PixelFormat;
15import android.media.Image;
16import android.media.ImageReader;
17import android.media.projection.MediaProjection;
18import android.media.projection.MediaProjectionManager;
19import android.os.Build;
20import android.os.Binder;
21import android.os.IBinder;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.util.DisplayMetrics;
25import android.util.Log;
26
27
28public class QtScreenCaptureService extends Service {
29 // Lock for synchronization
30 private final Object mServiceStopLock = new Object();
31 private boolean mServiceStopped = false;
32 private static final String QtTAG = "QtScreenCaptureService";
33 private static final String CHANNEL_ID = "ScreenCaptureChannel";
34 private static final String VIRTUAL_DISPLAY_NAME = "ScreenCapture";
35 private VirtualDisplay mVirtualDisplay = null;
36 private ImageReader mImageReader = null;
37 private static int MAX_FRAMES_NUMBER = 12;
38 private MediaProjection mMediaProjection = null;
39 private Handler mBackgroundHandler = null;
40 private HandlerThread mBackgroundThread = null;
41 private long mId;
42 private int mScreenWidth;
43 private int mScreenHeight;
44
45 static native void onScreenFrameAvailable(Image frame, long id);
46 static native void onErrorUpdate(String errorString, long id);
47
48 @Override
49 public void onCreate() {
50 super.onCreate();
51
52 NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Screen Capture",
53 NotificationManager.IMPORTANCE_LOW);
54 NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
55 if (manager != null)
56 manager.createNotificationChannel(channel);
57
58 Notification notification = new Notification.Builder(this, CHANNEL_ID)
59 .setSmallIcon(android.R.drawable.ic_notification_overlay)
60 .build();
61
62 // Start the service in the foreground with the created notification
63 startForeground(1, notification);
64 }
65
66 @Override
67 public int onStartCommand(Intent intent, int flags, int startId) {
68 synchronized (mServiceStopLock) {
69 if (mServiceStopped)
70 return START_STICKY;
71 return onStartCommandInternal(intent, flags, startId);
72 }
73 }
74
75 private int onStartCommandInternal(Intent intent, int flags, int startId) {
76 if (mServiceStopped)
77 return START_STICKY;
78
79 if (intent == null)
80 return START_STICKY;
81
82 int resultCode = intent.getIntExtra(QtScreenGrabber.RESULT_CODE, Activity.RESULT_CANCELED);
83 if (resultCode != Activity.RESULT_OK)
84 return START_STICKY;
85
86 Intent data = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ?
87 intent.getParcelableExtra(QtScreenGrabber.DATA) :
88 intent.getParcelableExtra(QtScreenGrabber.DATA, Intent.class);
89 mId = intent.getLongExtra(QtScreenGrabber.ID, -1);
90 if (data == null || mId == -1) {
91 onErrorUpdate("Cannot parse Intent. Screen capture not started", mId);
92 return START_STICKY;
93 }
94
95 mScreenWidth = intent.getIntExtra(QtScreenGrabber.WIDTH, 0);
96 mScreenHeight = intent.getIntExtra(QtScreenGrabber.HEIGHT, 0);
97 if (mScreenWidth <= 0 || mScreenHeight <= 0) {
98 onErrorUpdate("Wrong Screen size. Screen capture not started", mId);
99 return START_STICKY;
100 }
101
102 try {
103 MediaProjectionManager mgr = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
104 mMediaProjection = mgr.getMediaProjection(resultCode, data);
105 mMediaProjection.registerCallback(new MediaProjection.Callback() {
106 @Override
107 public void onStop() {
108 stopScreenCapture();
109 super.onStop();
110 }
111 }, null);
112 startScreenCapture();
113 } catch (IllegalStateException | SecurityException e) {
114 onErrorUpdate("Cannot start MediaProjection: " + e.toString(), mId);
115 }
116
117 return START_STICKY;
118 }
119
120 ImageReader.OnImageAvailableListener mScreenFrameListener = new ImageReader.OnImageAvailableListener() {
121 @Override
122 public void onImageAvailable(ImageReader reader) {
123 try {
124 Image image = reader.acquireLatestImage();
125 if (image != null)
126 onScreenFrameAvailable(image, mId);
127 else
128 Log.w(QtTAG, "Null frame acquired. Skip it");
129 } catch (Exception e) {
130 Log.w(QtTAG, "The frame cannot be acquired: " + e);
131 }
132 }
133 };
134
135 private void startScreenCapture()
136 {
137 if (mMediaProjection == null)
138 return;
139
140 mBackgroundThread = new HandlerThread("ScreenCaptureThread");
141 mBackgroundThread.start();
142 mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
143 mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, MAX_FRAMES_NUMBER);
144
145 mVirtualDisplay = mMediaProjection.createVirtualDisplay(VIRTUAL_DISPLAY_NAME,
146 mScreenWidth, mScreenHeight, DisplayMetrics.DENSITY_MEDIUM,
147 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mImageReader.getSurface(),
148 null, mBackgroundHandler);
149 mImageReader.setOnImageAvailableListener(mScreenFrameListener, mBackgroundHandler);
150 }
151
152 void stopScreenCapture()
153 {
154 synchronized (mServiceStopLock) {
155 if (mServiceStopped)
156 return;
157 mServiceStopped = true;
158
159 if (mImageReader != null)
160 mImageReader.setOnImageAvailableListener(null, mBackgroundHandler);
161
162 if (mVirtualDisplay != null) {
163 mVirtualDisplay.release();
164 mVirtualDisplay = null;
165 }
166 if (mBackgroundHandler != null) {
167 mBackgroundHandler.getLooper().quitSafely();
168 mBackgroundHandler = null;
169 }
170
171 if (mBackgroundThread != null) {
172 mBackgroundThread.quitSafely();
173 try {
174 mBackgroundThread.join();
175 } catch (InterruptedException e) {
176 Log.w(QtTAG, "The thread is interrupted. Cannot join: " + e);
177 }
178 mBackgroundThread = null;
179 }
180 }
181 }
182
183 private final IBinder binder = new ScreenCaptureBinder();
184
185 class ScreenCaptureBinder extends Binder {
186 QtScreenCaptureService getService() {
187 return QtScreenCaptureService.this;
188 }
189 }
190
191 @Override
192 public IBinder onBind(Intent intent) {
193 return binder;
194 }
195
196 @Override
197 public void onDestroy() {
198 super.onDestroy();
199
200 stopScreenCapture();
201 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
202 stopForeground(true);
203 else
204 stopForeground(STOP_FOREGROUND_REMOVE);
205
206 if (mMediaProjection != null) {
207 mMediaProjection.stop();
208 mMediaProjection = null;
209 }
210 }
211}
IOBluetoothL2CAPChannel * channel
[vector_of_multirole_objects_0]
Definition main.cpp:188
QVideoFrameFormat::PixelFormat PixelFormat
QImageReader reader("image.png")
[1]
static void onScreenFrameAvailable(JNIEnv *env, jobject obj, QtJniTypes::Image image, jlong id)
static void onErrorUpdate(JNIEnv *env, jobject obj, QString errorString, jlong id)
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
[0]
GLbitfield flags
@ Handler
EGLImageKHR image
[4]
QFrame frame
[0]
QNetworkAccessManager manager
[0]