Take a Screenshot Using Mediaprojection

How to take a Screenshot from a background-service class using MediaProjection API?

This is tricky way to achieve this.

First of all you need to create transparent background theme like.

    <style name="transparentTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:background">#00000000</item> <!-- Or any transparency or color you need -->
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">#00000000</item>
<item name="android:statusBarColor" tools:ignore="NewApi">#00000000</item>
</style>

Now you need to add this apply this theme on your ScreenShotActivity in Manifest file.

  <activity
android:name=".Activities.ScreenShotActivity"
android:theme="@style/transparentTheme" />
<activity

Your ScreenShotActivity Class.

public class ScreenShotActivity extends Activity {

private static final int videoTime = 5000;
private static final int REQUEST_CODE = 1000;
private static final int REQUEST_PERMISSION = 1000;
private static final SparseIntArray ORIENTATION = new SparseIntArray();
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
private VirtualDisplay virtualDisplay;
private ScreenShotActivity.MediaProjectionCallback mediaProjectionCallback;
private MediaRecorder mediaRecorder;
PostWebAPIData postWebAPIData;
private int mScreenDensity;
private static int DISPLAY_WIDTH = 720;
private static int DISPLAY_HEIGHT = 1280;

static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}

private String screenShotUri = "";

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_shot);
init();
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void init() {
//Screen tracking Code Started here..............................
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mScreenDensity = metrics.densityDpi;
postWebAPIData = new PostWebAPIData();
DISPLAY_HEIGHT = metrics.heightPixels;
DISPLAY_WIDTH = metrics.widthPixels;

mediaRecorder = new MediaRecorder();
mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (ContextCompat.checkSelfPermission(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ ContextCompat.checkSelfPermission(ScreenShotActivity.this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
ActivityCompat.shouldShowRequestPermissionRationale(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
} else {
ActivityCompat.requestPermissions(ScreenShotActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
}, REQUEST_PERMISSION);
}
} else {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
toogleScreenShare();
}
}, 500);
}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void toogleScreenShare() {
initRecorder();
recordScreen();
}

public void getPathScreenShot(String filePath) {
FFmpegMediaMetadataRetriever med = new FFmpegMediaMetadataRetriever();

med.setDataSource(filePath);
Bitmap bmp = med.getFrameAtTime(2 * 1000000, FFmpegMediaMetadataRetriever.OPTION_CLOSEST);
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + new StringBuilder("/screenshot").append(".bmp").toString();

File myDir = new File(myPath);
myDir.mkdirs();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-" + n + ".jpg";
File file = new File(myDir, fname);
Log.i(TAG, "" + myDir);
if (myDir.exists())
myDir.delete();
try {
FileOutputStream out = new FileOutputStream(myDir);
bmp.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
postScreenShot(myPath);
}


@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void recordScreen() {
if (mediaProjection == null) {
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
} else {
virtualDisplay = createVirtualDisplay();
mediaRecorder.start();
onBackPressed();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mediaRecorder.stop();
mediaRecorder.reset();
stopRecordScreen();
destroyMediaProjection();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
getPathScreenShot(screenShotUri);
}
}, 2000);
}
}, videoTime);
}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private VirtualDisplay createVirtualDisplay() {
return mediaProjection.createVirtualDisplay("MainActivity", DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mediaRecorder.getSurface(), null, null);
}

private void initRecorder() {
try {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

screenShotUri = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + new StringBuilder("/screenshot").append(".mp4").toString();

mediaRecorder.setOutputFile(screenShotUri);
mediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(5);

int rotation = getWindowManager().getDefaultDisplay().getRotation();
int orientation = ORIENTATION.get(rotation + 90);
mediaRecorder.setOrientationHint(orientation);
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
Log.d("ExceptionOccured", "" + e.getMessage());
}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != REQUEST_CODE) {
stopService(new Intent(this, BackgroundService.class));
startService(new Intent(this, BackgroundService.class));
Toast.makeText(ScreenShotActivity.this, "Unknown Error", Toast.LENGTH_SHORT).show();
Log.d("Livetracking", "ScreenShot" + requestCode + " " + resultCode + " " + data);
return;
}
if (resultCode != RESULT_OK) {
stopService(new Intent(this, BackgroundService.class));
startService(new Intent(this, BackgroundService.class));
Toast.makeText(ScreenShotActivity.this, "Permission denied" + requestCode, Toast.LENGTH_SHORT).show();
Log.d("Livetracking", "Screenshot" + requestCode + " " + resultCode + " " + data);
return;
}
Log.d("Livetracking", "Screenshot" + requestCode + " " + resultCode + " " + data);

mediaProjectionCallback = new ScreenShotActivity.MediaProjectionCallback();
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
mediaProjection.registerCallback(mediaProjectionCallback, null);
virtualDisplay = createVirtualDisplay();
mediaRecorder.start();
onBackPressed();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mediaRecorder.stop();
mediaRecorder.reset();
stopRecordScreen();
destroyMediaProjection();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
getPathScreenShot(screenShotUri);
}
}, 2000);
}
}, videoTime);
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.AppTask> tasks = am.getAppTasks();
if (tasks != null && tasks.size() > 0) {
Log.d("RemovingApp", "recent");
tasks.get(0).setExcludeFromRecents(true);
}
}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private class MediaProjectionCallback extends MediaProjection.Callback {
@Override
public void onStop() {
mediaRecorder.stop();
mediaRecorder.reset();
mediaProjection = null;
stopRecordScreen();
destroyMediaProjection();
if (mediaProjection != null) {
destroyMediaProjection();
}
super.onStop();
}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void stopRecordScreen() {
if (virtualDisplay == null) {
virtualDisplay.release();
if (mediaProjection != null) {
destroyMediaProjection();
}
return;

}
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void destroyMediaProjection() {
if (mediaProjection != null) {
mediaProjection.unregisterCallback(mediaProjectionCallback);
mediaProjection.stop();
mediaProjection = null;
}
}
}

add these permissions in your Manifest file.

   <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Now the magic start here you need to call your ScreenShotActivity from your service like this.

      Intent dialogIntent = new Intent(BackgroundService.this, ScreenShotActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(dialogIntent);

Can Media Projection api take a screenshot from my home screen on lollipop?

Check some code from here. This code save bitmaps from the surface view related with Media Projection.

Media projection taking screenshots every second (or half a second)

It is not regular because a screenshot is taken only if a new frame is available (something has changed on the screen). As far as I know it is not possible to take screenshots at regular intervals using Media Projection.

How to properly take a screenshot, globally?

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?

You have black margins around your screenshot because you are not using realSize of the window you're in. To solve this:

  1. Get the real size of the window:


final Point windowSize = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getRealSize(windowSize);



  1. Use that to create your image reader:


imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);



  1. This third step may not be required but I have seen otherwise in my app's production code (which runs on a variety of android devices out there). When you acquire an image for ImageReader and create a bitmap out of it. Crop that bitmap using the window size using below code.


// fix the extra width from Image
Bitmap croppedBitmap;
try {
croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, windowSize.x, windowSize.y);
} catch (OutOfMemoryError e) {
Timber.d(e, "Out of memory when cropping bitmap of screen size");
croppedBitmap = bitmap;
}
if (croppedBitmap != bitmap) {
bitmap.recycle();
}


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.

To capture screen/take screenshot you need an object of MediaProjection. To create such object, you need pair of resultCode (int) and Intent. You already know how these objects are acquired and cache those in your ScreenshotManager class.

Coming back to taking screenshots of any app, you need to follow the same procedure of getting these variables resultCode and Intent but instead of caching it locally in your class variables, start a background service and pass these variables to the same like any other normal parameters. Take a look at how Telecine does it here. When this background service is started it can provide a trigger (a notification button) to the user which when clicked, will perform the same operations of capturing screen/taking screenshot as you are doing in your ScreenshotManager class.

Why is it so slow? Is there a way to improve it?

How much slow is it to your expectations? My use case for Media projection API is to take a screenshot and present it to the user for editing. For me the speed is decent enough. One thing I feel worth mentioning is that the ImageReader class can accept a Handler to a thread in setOnImageAvailableListener. If you provide a handler there, onImageAvailable callback will be triggered on the handler thread instead of the one that created the ImageReader. This will help you in NOT creating a AsyncTask (and starting it) when an image is available instead the callback itself will happen on a background thread. This is how I create my ImageReader:



private void createImageReader() {
startBackgroundThread();
imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
ImageHandler imageHandler = new ImageHandler(context, domainModel, windowSize, this, notificationManager, analytics);
imageReader.setOnImageAvailableListener(imageHandler, backgroundHandler);
}

private void startBackgroundThread() {
backgroundThread = new HandlerThread(NAME_VIRTUAL_DISPLAY);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}




Related Topics



Leave a reply



Submit