Android Media Projection Screen Capture Works Differently On Real Device

1.5k views Asked by At

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();
}

}

0

There are 0 answers