mtsahakis / MediaProjectionDemo

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

Multiple Screenshots #27

Open zees98 opened 2 years ago

zees98 commented 2 years ago

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