Code Monkey home page Code Monkey logo

mediaprojectiondemo's Introduction

Android Lollipop 5.0 MediaProjectionManager Demo

This is a sample app that demonstrates how to capture screenshots based on the MediaProjection API. More on the API can be found in https://developer.android.com/reference/android/media/projection/package-summary.html Clone and import the project in Android Studio. No special dependencies and extra libraries required. Note that in order to run the code you need to create a device running Lollipop and above.

mediaprojectiondemo's People

Contributors

mrshakes avatar mtsahakis avatar nmsutton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mediaprojectiondemo's Issues

Increase number of screenshots per sec

Hello There,
I am kind of newbie and looking to improve number of frames captured per second. Currently, getting 9-10FPS. How can I improve to capture more screenshots. Sorry if it is dumb question.

Some questions: memory leak, looper, moving to service...

  1. Since many fields (mHandler for example) are in the activity, won't it mean that it can leak upon screen orientation change?
  2. Isn't it the same to just instantiate the handler as : "mHandler = new Handler() " , compared to the looper in the code?
  3. What would it take to move all the needed code to a service, that upon calling a specific function there will trigger a screenshot?

Having build error

When importing and trying to build the sample project, I get this error:

Information:Gradle tasks [:app:assembleDebug]
C:\Users\User\Desktop\android-hidden-api\sample\android-hidden-api\src\main\java\com\anggrayudi\hiddenapi\InternalAccessor.java
Error:(43, 73) error: package com.android.internal.R does not exist
Error:(44, 73) error: package com.android.internal.R does not exist
Error:(45, 73) error: package com.android.internal.R does not exist
Error:(46, 73) error: package com.android.internal.R does not exist
Error:(47, 73) error: package com.android.internal.R does not exist
Error:(48, 73) error: package com.android.internal.R does not exist
Error:(49, 73) error: package com.android.internal.R does not exist
Error:(50, 73) error: package com.android.internal.R does not exist
Error:(51, 73) error: package com.android.internal.R does not exist
Error:(52, 73) error: package com.android.internal.R does not exist
Error:(53, 73) error: package com.android.internal.R does not exist
Error:(54, 73) error: package com.android.internal.R does not exist
Error:(55, 73) error: package com.android.internal.R does not exist
Error:(56, 73) error: package com.android.internal.R does not exist
Error:(57, 73) error: package com.android.internal.R does not exist
Error:(58, 73) error: package com.android.internal.R does not exist
Error:org.gradle.api.internal.tasks.compile.CompilationFailedException: Compilation failed; see the compiler error output for details.

Question: best way to take a single screenshot and stop

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.

writting buffer to bitmap

when creating display it takes the right resolution 1920x1080 but when writing image.. image gets wrong resolution 1920x1088
This happens only on portrait rotation.. on landscape its good!
well i used your patch for removing black from image.. but it seems to be quite slow.. is there any way more efficient to do this ?

No JPEG header detected

Thanks for sharing your code.

I tested the app on a Nexus 9 and on a Genymotion emulator (both with API 21) and I wasn't able to retrieve a valid image. I kept getting these logs:

ImageReader_imageSetup: Receiving JPEG in HAL_PIXEL_FORMAT_RGBA_8888 buffer.
Image_getJpegSize: No JPEG header detected, defaulting to size=width=11993088

Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION

Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
at android.os.Parcel.createException(Parcel.java:2071)
at android.os.Parcel.readException(Parcel.java:2039)
at android.os.Parcel.readException(Parcel.java:1987)
at android.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:231)
at android.media.projection.MediaProjection.(MediaProjection.java:58)
at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:104)
at com.kloudq.apps.rrov5.activity.LoginActivity.onActivityResult(LoginActivity.java:1361)
at android.app.Activity.dispatchActivityResult(Activity.java:8110)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4838)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886) 
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
at android.os.Handler.dispatchMessage(Handler.java:107) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:476)
at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:135)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)

Multiple Screenshots

Greetings
I am having the issue of getting multiple screenshots being captured. Whenever I click something on the screenshot 9 to 10 screenshots are captured.

I would like for it to take just one screenshot.

P.S. I converted your code to kotlin and integrated it with Flutter. I am not a native developer, but I do understand a lot of things about the code but maybe I am overlooking something.

Capture Service

package com.example.native_example

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Service
import android.app.Notification
import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.Image
import android.media.ImageReader
import android.media.ImageReader.OnImageAvailableListener
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.Log
import android.view.Display
import android.view.OrientationEventListener
import android.view.WindowManager
import com.example.native_example.NotificationUtils.getNotification

import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.ByteBuffer
import java.util.*

class CaptureSerivce : Service() {
    private val TAG = "ScreenCaptureService"
    private val RESULT_CODE = "RESULT_CODE"
    private val DATA = "DATA"
    private val ACTION = "ACTION"
    private val START = "START"
    private val STOP = "STOP"
    private val SCREENCAP_NAME = "screencap"
    private var IMAGES_PRODUCED = 0
    private var mMediaProjection: MediaProjection? = null
    private var mStoreDir: String? = null
    private var mImageReader: ImageReader? = null
    private var mHandler: Handler? = null
    private var mDisplay: Display? = null
    private var mVirtualDisplay: VirtualDisplay? = null
    private var mDensity = 0
    private var mWidth = 0
    private var mHeight = 0
    private var mRotation = 0
    private var mOrientationChangeCallback: OrientationChangeCallback? = null
    fun getStartIntent(context: Context?, resultCode: Int, data: Intent?): Intent? {
        val intent = Intent(context, CaptureSerivce::class.java)
        intent.putExtra(ACTION, START)
        intent.putExtra(RESULT_CODE, resultCode)
        intent.putExtra(DATA, data)
        return intent
    }
   fun getStopIntent(context: Context?): Intent? {
        val intent = Intent(context, CaptureSerivce::class.java)
        intent.putExtra(ACTION, STOP)
        return intent
    }
    private fun isStartCommand(intent: Intent): Boolean {
        return (intent.hasExtra(RESULT_CODE) && intent.hasExtra(DATA)
                && intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), START))
    }
    private fun isStopCommand(intent: Intent): Boolean {
        return intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), STOP)
    }
    private fun getVirtualDisplayFlags(): Int {
        return DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
    }
    inner  class ImageAvailableListener : OnImageAvailableListener {
        override fun onImageAvailable(reader: ImageReader) {
            try {
                mImageReader?.acquireLatestImage().use { image ->
                    if (image != null) {
                        val planes: Array<Image.Plane> = image.getPlanes()
                        val buffer: ByteBuffer = planes[0].getBuffer()
                        val pixelStride: Int = planes[0].getPixelStride()
                        val rowStride: Int = planes[0].getRowStride()
                        val rowPadding: Int = rowStride - pixelStride * mWidth
                        // create bitmap
                        val bitmap = Bitmap.createBitmap(
                            mWidth + rowPadding / pixelStride,
                            mHeight,
                            Bitmap.Config.ARGB_8888
                        )
                        bitmap.copyPixelsFromBuffer(buffer)
                        // write bitmap to a file
                        val fos = FileOutputStream("$mStoreDir/myscreen_$IMAGES_PRODUCED.png")
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
                        IMAGES_PRODUCED++
                        Log.e(TAG, "captured image: $IMAGES_PRODUCED")
                        try {
                            fos.close()
                        } catch (ioe: IOException) {
                            ioe.printStackTrace()
                        }
                        if (bitmap != null) {
                            bitmap!!.recycle()
                        }
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    inner class OrientationChangeCallback internal constructor(context: Context?) :
        OrientationEventListener(context) {
        override fun onOrientationChanged(orientation: Int) {
            val rotation: Int = mDisplay!!.getRotation()
            if (rotation != mRotation) {
                mRotation = rotation
                try {
                    // clean up
                    mVirtualDisplay?.release()
                    mImageReader?.setOnImageAvailableListener(null, null)
                    // re-create virtual display depending on device width / height
                    createVirtualDisplay()
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }
            }
        }
    }

    inner class MediaProjectionStopCallback : MediaProjection.Callback() {
        override fun onStop() {
            Log.e(TAG, "stopping projection.")
            mHandler!!.post(Runnable {
                mVirtualDisplay?.release()
                mImageReader?.setOnImageAvailableListener(null, null)
                mOrientationChangeCallback?.disable()
                mMediaProjection?.unregisterCallback(this@MediaProjectionStopCallback)
            })
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        // create store dir
        val externalFilesDir: File? = getExternalFilesDir(null)
        if (externalFilesDir != null) {
            mStoreDir = externalFilesDir.getAbsolutePath().toString() + "/screenshots/"
            val storeDirectory = File(mStoreDir)
            if (!storeDirectory.exists()) {
                val success: Boolean = 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
        object : Thread() {
            override fun run() {
//                Looper.prepare()
//                mHandler = Handler()
//                Looper.loop()
            }
        }.start()
    }
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        when {
            isStartCommand(intent) -> {
                // create notification
                val pair = NotificationUtils.getNotification(this)
                val first = pair.first
                val second = pair.second
                startForeground(first, second)
                // start projection
                val resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED)
                val data = intent.getParcelableExtra<Intent>(DATA)
                startProjection(resultCode, data)
            }
            isStopCommand(intent) -> {
                stopProjection()
                stopSelf()
            }
            else -> {
                stopSelf()
            }
        }
        return START_NOT_STICKY
    }
    private fun startProjection(resultCode: Int, data: Intent?) {
        val mpManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        if (mMediaProjection == null) {
            mMediaProjection = mpManager.getMediaProjection(resultCode, data!!)
            if (mMediaProjection != null) {
                // display metrics
                mDensity = Resources.getSystem().getDisplayMetrics().densityDpi
                val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
                mDisplay = windowManager.defaultDisplay
                // create virtual display depending on device width / height
                createVirtualDisplay()
                // register orientation change callback
                mOrientationChangeCallback = OrientationChangeCallback(this)
                if (mOrientationChangeCallback!!.canDetectOrientation()) {
                    mOrientationChangeCallback!!.enable()
                }
                // register media projection stop callback
                mMediaProjection!!.registerCallback(MediaProjectionStopCallback(), mHandler)
            }
        }
    }

    private fun stopProjection() {
        mHandler?.post(Runnable {
            mMediaProjection?.stop()
        })
    }

    private fun createVirtualDisplay() {
        // get width and height
        mWidth = Resources.getSystem().getDisplayMetrics().widthPixels
        mHeight = Resources.getSystem().getDisplayMetrics().heightPixels

        // start capture reader
        mImageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.YV12, 2)
        mVirtualDisplay = mMediaProjection!!.createVirtualDisplay(
            SCREENCAP_NAME, mWidth, mHeight,
            mDensity, getVirtualDisplayFlags(), mImageReader!!.surface, null, mHandler
        )
        mImageReader!!.setOnImageAvailableListener(ImageAvailableListener(), mHandler)
    }
}

Main Activity


import android.content.*
import android.media.projection.MediaProjectionManager
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel


class MainActivity: FlutterActivity() {
    private val REQUEST_CODE = 100
    private val CHANNEL = "native_channel"
    private lateinit var channel: MethodChannel

    private lateinit var mediaProjectionManager : MediaProjectionManager

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        
        channel.setMethodCallHandler { call, result ->
            if(call.method == "test"){
                startProjection();

                stopProjection();

                result.success("Zeeshan was Successful")
            }
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                startService(
                   CaptureSerivce().getStartIntent(this,  resultCode, data)
                )
                Log.d("", "Service started")
            }
        }
    }
    private fun startProjection() {
        val mProjectionManager =
            getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE)
    }

    private fun stopProjection() {
        startService(CaptureSerivce().getStopIntent(this))
    }
}

Would appreciate your help. TIA

Can't run this demo. Could not find com.android.tools.build:aapt2:3.3.2-5309881

Hi there and thank you for this interesting demo app. Unfortunately I cant get it working. WhenI press "Run" I get the error: "Could not find com.android.tools.build:aapt2:3.3.2-5309881". I've googled that and found that I should add
"google()"
into the section:
buildscript {
repositories {
in file build.gradle
This was already done, but still the application won't complie. Could you tell me, what I'm doing wrong? I'm so brand new to Android. Thank you...

Bug: black margins on left and right

Just noticed that the sample produces files with black left&right margins:

myscreen_5

Why does it occur?
What should be done to fix it?
Tested on Nexus 5x with Android 7.1.2

Slows down in its own activity

Many thanks for this code example. My idea is to use it as a base for screen-mirroring.
I noted that when the sample app's activity is the forefront, the capture of the images slows down drastically whereas if I move on to another screen (of another app or the home-screen) the image-creation is very fast (numerous ones within a second).

Any idea why this is happening and how this can be resolved? Could a service resolve this problem?

Edit: I tried your activity/service code but with the same result.

Thank you.

White Bitmap on some devices

I have a product for screen capture feature. I have received errors on some devices with unknown causes.
Bitmap received no data, it's white.
Especially when turning on screen recording mode on the phone, it works (data bitmap has data).
I want to take a screenshot in a service.
I do not want it to require the "App want recording screen" permission many times, Should have saved the resultCode and Data data for the first time and used them.
On my device, it works well. The Error was on: Asus zenfone max pro m1-android 9, Xiaomi redmi note 5- android 9. (Screenshot 2 still works; 1,3,4 fail).
Please tell me the reason and how to fix it.
Here is my sample: https://github.com/kobidy1102/ScreenShot.git

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.