When I Run The Code Below, Continuous Screen Recording Is Working In The Virtual Device And Capturing The Pictures.
However, when the code is run on the real device, they are all the same, except for a few of the captured images (with the exception of the images captured in the first second).
I Couldn't Find Why It Doesn't Work Stably On Real Device.
I Tried Different Picture Formats, I Tried Different Screen Resolution. I also tried different API versions. I Tried So Many Things I Can't Count.
But I Couldn't Understand Why It Captures A Few Different Images Every Second On The Virtual Device And Re-Records The Images It Captured In The Real Device For Minutes Only In The First Second.
Maybe If You Examine The Code, You'll Notice Something I Didn't Notice.
Thank you for the support.
Service.java
public class ScreenCaptureService extends Service {
private static final String TAG = "ScreenCaptureService";
private static final String RESULT_CODE = "RESULT_CODE";
private static final String DATA = "DATA";
private static final String ACTION = "ACTION";
private static final String START = "START";
private static final String STOP = "STOP";
private static final String SCREENCAP_NAME = "screencap";
private static int IMAGES_PRODUCED;
private MediaProjection mMediaProjection;
private String mStoreDir;
private ImageReader mImageReader;
private Handler mHandler;
private Display mDisplay;
private VirtualDisplay mVirtualDisplay;
private int mDensity;
private int mWidth;
private int mHeight;
private int mRotation;
private OrientationChangeCallback mOrientationChangeCallback;
public static Intent getStartIntent(Context context, int resultCode, Intent data) {
Intent intent = new Intent(context, ScreenCaptureService.class);
intent.putExtra(ACTION, START);
intent.putExtra(RESULT_CODE, resultCode);
intent.putExtra(DATA, data);
return intent;
}
public static Intent getStopIntent(Context context) {
Intent intent = new Intent(context, ScreenCaptureService.class);
intent.putExtra(ACTION, STOP);
return intent;
}
private static boolean isStartCommand(Intent intent) {
return intent.hasExtra(RESULT_CODE) && intent.hasExtra(DATA)
&& intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), START);
}
private static boolean isStopCommand(Intent intent) {
return intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), STOP);
}
private static int getVirtualDisplayFlags() {
return DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
}
private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
FileOutputStream fos = null;
Bitmap bitmap = null;
try (Image image = mImageReader.acquireLatestImage()) {
if (image != null) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
// create bitmap
bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
// write bitmap to a file
fos = new FileOutputStream(mStoreDir + "/myscreen_" + IMAGES_PRODUCED + ".png");
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
IMAGES_PRODUCED++;
Log.e(TAG, "captured image: " + IMAGES_PRODUCED);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (bitmap != null) {
bitmap.recycle();
}
}
}
}
private class OrientationChangeCallback extends OrientationEventListener {
OrientationChangeCallback(Context context) {
super(context);
}
@Override
public void onOrientationChanged(int orientation) {
final int rotation = mDisplay.getRotation();
if (rotation != mRotation) {
mRotation = rotation;
try {
// clean up
if (mVirtualDisplay != null) mVirtualDisplay.release();
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
// re-create virtual display depending on device width / height
createVirtualDisplay();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class MediaProjectionStopCallback extends MediaProjection.Callback {
@Override
public void onStop() {
Log.e(TAG, "stopping projection.");
mHandler.post(new Runnable() {
@Override
public void run() {
if (mVirtualDisplay != null) mVirtualDisplay.release();
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
if (mOrientationChangeCallback != null) mOrientationChangeCallback.disable();
mMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
}
});
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// create store dir
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null) {
mStoreDir = externalFilesDir.getAbsolutePath() + "/screenshots/";
File storeDirectory = new File(mStoreDir);
if (!storeDirectory.exists()) {
boolean success = storeDirectory.mkdirs();
if (!success) {
Log.e(TAG, "failed to create file storage directory.");
stopSelf();
}
}
} else {
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
stopSelf();
}
// start capture handling thread
new Thread() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler();
Looper.loop();
}
}.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (isStartCommand(intent)) {
// create notification
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
startForeground(notification.first, notification.second);
// start projection
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
Intent data = intent.getParcelableExtra(DATA);
startProjection(resultCode, data);
} else if (isStopCommand(intent)) {
stopProjection();
stopSelf();
} else {
stopSelf();
}
return START_NOT_STICKY;
}
private void startProjection(int resultCode, Intent data) {
MediaProjectionManager mpManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (mMediaProjection == null) {
mMediaProjection = mpManager.getMediaProjection(resultCode, data);
if (mMediaProjection != null) {
// display metrics
mDensity = Resources.getSystem().getDisplayMetrics().densityDpi;
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mDisplay = windowManager.getDefaultDisplay();
// create virtual display depending on device width / height
createVirtualDisplay();
// register orientation change callback
mOrientationChangeCallback = new OrientationChangeCallback(this);
if (mOrientationChangeCallback.canDetectOrientation()) {
mOrientationChangeCallback.enable();
}
// register media projection stop callback
mMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
}
}
}
private void stopProjection() {
if (mHandler != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMediaProjection != null) {
mMediaProjection.stop();
}
}
});
}
}
@SuppressLint("WrongConstant")
private void createVirtualDisplay() {
// get width and height
mWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
mHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
// start capture reader
mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
mVirtualDisplay = mMediaProjection.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight,
mDensity, getVirtualDisplayFlags(), mImageReader.getSurface(), null, mHandler);
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mHandler);
}
}
Activity page:
public class ScreenCaptureActivity extends Activity {
private static final int REQUEST_CODE = 100;
/****************************************** Activity Lifecycle methods ************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// start projection
Button startButton = findViewById(R.id.startButton);
startButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startProjection();
}
});
// stop projection
Button stopButton = findViewById(R.id.stopButton);
stopButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
stopProjection();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
startService(com.capture.mediaprojection.ScreenCaptureService.getStartIntent(this, resultCode, data));
}
}
}
/****************************************** UI Widget Callbacks *******************************/
private void startProjection() {
MediaProjectionManager mProjectionManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}
private void stopProjection() {
startService(com.capture.mediaprojection.ScreenCaptureService.getStopIntent(this));
}
}
Notification page:
public class NotificationUtils {
public static final int NOTIFICATION_ID = 1337;
private static final String NOTIFICATION_CHANNEL_ID = "com.capture.mediaprojection.app";
private static final String NOTIFICATION_CHANNEL_NAME = "com.capture.mediaprojection.app";
public static Pair<Integer, Notification> getNotification(@NonNull Context context) {
createNotificationChannel(context);
Notification notification = createNotification(context);
NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
return new Pair<>(NOTIFICATION_ID, notification);
}
@TargetApi(Build.VERSION_CODES.O)
private static void createNotificationChannel(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
}
private static Notification createNotification(@NonNull Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setSmallIcon(R.drawable.ic_camera);
builder.setContentTitle(context.getString(R.string.app_name));
builder.setContentText(context.getString(R.string.recording));
builder.setOngoing(true);
builder.setCategory(Notification.CATEGORY_SERVICE);
builder.setPriority(Notification.PRIORITY_LOW);
builder.setShowWhen(true);
return builder.build();
}
}