mtsahakis / MediaProjectionDemo

One Activity sample app of using Android Lollipop MediaProjection API to capture device screenshots
Other
210 stars 76 forks source link

Question: best way to take a single screenshot and stop #9

Closed AndroidDeveloperLB closed 3 years ago

AndroidDeveloperLB commented 7 years ago

Just wanted to ask what would need to be changed, to get a single screenshot and be done with it till next time it will need to take a screenshot (if at all), avoiding the notification when not needed.

I tried to call "stopProjection" right in the "onImageAvailable" function, but it still got more than one image.

mtsahakis commented 7 years ago

Hmm, interesting one.

Why don't you add a boolean flag like shouldCapture and then, after the first successful image stop projection and set this to false so that no additional images will be captured. Something like

private boolean shouldCapture;

if (image != null && shouldCapture) {
// do the capturing
// call a stop method
stopProjection();
}

private void startProjection() {
        shouldCapture = true;
        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}

private void stopProjection() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                shouldCapture = false;
                if (sMediaProjection != null) {
                    sMediaProjection.stop();
                }
            }
        });
    }
AndroidDeveloperLB commented 7 years ago

I think it's all because of the multiple threads that work here. Here's what I did. I made a manager class for the screenshots, which by a single call to "takeScreenshot" (after the app was given the permission to record screen, using "requestScreenshotPermission" and then "onActivityResult") :

public class ScreenshotManager {
    private static final String SCREENCAP_NAME = "screencap";
    private static final int VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
    public static final ScreenshotManager INSTANCE = new ScreenshotManager();
    private Intent mIntent;

    private ScreenshotManager() {
    }

    public void requestScreenshotPermission(@NonNull Activity activity, int requestId) {
        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        activity.startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), requestId);
    }

    public void onActivityResult(int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK && data != null)
            mIntent = data;
        else mIntent = null;
    }

    @UiThread
    public boolean takeScreenshot(@NonNull Context context) {
        if (mIntent == null)
            return false;
        final MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mIntent);
        if (mediaProjection == null)
            return false;
        final int density = context.getResources().getDisplayMetrics().densityDpi;
        final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        final Point size = new Point();
        display.getSize(size);
        final int width = size.x, height = size.y;
        final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
        final VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(SCREENCAP_NAME, width, height, density, VIRTUAL_DISPLAY_FLAGS, imageReader.getSurface(), null, null);
        imageReader.setOnImageAvailableListener(new OnImageAvailableListener() {
            @Override
            public void onImageAvailable(final ImageReader reader) {
                Log.d("AppLog", "onImageAvailable");
                mediaProjection.stop();
                new AsyncTask<Void, Void, Bitmap>() {
                    @Override
                    protected Bitmap doInBackground(final Void... params) {
                        Image image = null;
                        Bitmap bitmap = null;
                        try {
                            image = reader.acquireLatestImage();
                            if (image != null) {
                                Plane[] planes = image.getPlanes();
                                ByteBuffer buffer = planes[0].getBuffer();
                                int pixelStride = planes[0].getPixelStride(), rowStride = planes[0].getRowStride(), rowPadding = rowStride - pixelStride * width;
                                bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Config.ARGB_8888);
                                bitmap.copyPixelsFromBuffer(buffer);
                                return bitmap;
                            }
                        } catch (Exception e) {
                            if (bitmap != null)
                                bitmap.recycle();
                            e.printStackTrace();
                        }
                        if (image != null)
                            image.close();
                        reader.close();
                        return null;
                    }

                    @Override
                    protected void onPostExecute(final Bitmap bitmap) {
                        super.onPostExecute(bitmap);
                        Log.d("AppLog", "Got bitmap?" + (bitmap != null));
                    }
                }.execute();
            }
        }, null);
        mediaProjection.registerCallback(new Callback() {
            @Override
            public void onStop() {
                super.onStop();
                if (virtualDisplay != null)
                    virtualDisplay.release();
                imageReader.setOnImageAvailableListener(null, null);
                mediaProjection.unregisterCallback(this);
            }
        }, null);
        return true;
    }
}
mtsahakis commented 7 years ago

Should work. You definitely need some other reader than the one provided in the demo. Can't help but wonder what would happen if first image grabbed is null though, this is why I was thinking of stopping projection after at least one image is captured. Edge case though, probably never happen in real life.

AndroidDeveloperLB commented 7 years ago

I have a few questions:

  1. Why could the image be null? It's in the onImageAvailable call, so there should be an image...
  2. Is it possible to make it somehow faster? Maybe avoid the re-initialization? Maybe let the notification stay? If so, how?