Skip to content

Instantly share code, notes, and snippets.

@mtsahakis
Last active November 3, 2021 04:48
Show Gist options
  • Save mtsahakis/a4dca46b80cb8ac2cd100a7a52f65b1d to your computer and use it in GitHub Desktop.
Save mtsahakis/a4dca46b80cb8ac2cd100a7a52f65b1d to your computer and use it in GitHub Desktop.
package com.mtsahakis.mediaprojectiondemo;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.WindowManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
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 isValidStartIntent(Intent intent) {
return intent.hasExtra(RESULT_CODE) && intent.hasExtra(DATA)
&& intent.hasExtra(ACTION) && intent.getStringExtra(ACTION).equals(START);
}
private static boolean isValidStopIntent(Intent intent) {
return intent.hasExtra(ACTION) && intent.getStringExtra(ACTION).equals(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) {
Image image = null;
FileOutputStream fos = null;
Bitmap bitmap = null;
try {
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();
}
if (image != null) {
image.close();
}
}
}
}
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 (isValidStartIntent(intent)) {
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
Intent data = intent.getParcelableExtra(DATA);
startProjection(resultCode, data);
}
if (isValidStopIntent(intent)) {
stopProjection();
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
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDensity = metrics.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();
}
}
});
}
}
private void createVirtualDisplay() {
// get width and height
Point size = new Point();
mDisplay.getSize(size);
mWidth = size.x;
mHeight = size.y;
// 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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment