Background
Since Android API 21, it's possible for apps to take screenshots globally and record the screen.
The problem
I've made a sample code out of all I've found on the Internet, but it has a few issues:
- It's quite slow. Maybe it's possible to avoid it, at least for multiple screenshots, by avoiding the notification of it being removed till really not needed.
- It has black margins on left and right, meaning something might be wrong with the calculations :
What I've tried
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.checkIfPossibleToRecordButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.requestScreenshotPermission(MainActivity.this, REQUEST_ID);
}
});
findViewById(R.id.takeScreenshotButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.takeScreenshot(MainActivity.this);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ID)
ScreenshotManager.INSTANCE.onActivityResult(resultCode, data);
}
}
layout/activity_main.xml
<LinearLayout
android:id="@+id/rootView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/checkIfPossibleToRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="request if possible"/>
<Button
android:id="@+id/takeScreenshotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="take screenshot"/>
</LinearLayout>
ScreenshotManager
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;
// start capture reader
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;
}
}
The questions
Well it's about the problems:
- Why is it so slow? Is there a way to improve it?
- How can I avoid, between taking screenshots, the removal of the notification of them? When can I remove the notification? Does the notification mean it constantly takes screenshots?
- Why does the output bitmap (currently I don't do anything with it, because it's still POC) have black margins in it? What's wrong with the code in this matter?
NOTE: I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.
EDIT: I've noticed that on CommonsWare website (here), it is said that the output bitmap is larger for some reason, but as opposed to what I've noticed (black margin in beginning AND end), it says it's supposed to be in the end:
For inexplicable reasons, it will be a bit larger, with excess unused pixels on each row on the end.
I've tried what was offered there, but it crashes with the exception "java.lang.RuntimeException: Buffer not large enough for pixels" .