I am a newbie in android programmin and write small application with Kotlin in Android Studio 2024. I need make screenshot of another application. I see MediaProjection as good way. My app has MainActivity and service. When executing
projection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
i error
“Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION”
and app crashes. I see many docs, add notifications, permissions, but no luck. Need help!
<code>package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.widget.Toast
import androidx.core.app.ServiceCompat
import com.example.screenshot.Capture
class MainActivity : Activity() {
private const val REQUEST_CAPTURE = 1
var projection: MediaProjection? = null
private lateinit var mediaProjectionManager: MediaProjectionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startServiceButton: Button = findViewById(R.id.start_service_button)
val stopServiceButton: Button = findViewById(R.id.stop_service_button)
mediaProjectionManager = getSystemService(Service.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent() , REQUEST_CAPTURE)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val intent = Intent(this, CaptureService::class.java)
if (requestCode == REQUEST_CAPTURE) {
if (resultCode == RESULT_OK) {
.setAction(CaptureService.ACTION_ENABLE_CAPTURE)
startForegroundService(intent)
projection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
Toast.makeText(this, "All OK", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show()
<code>package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.ServiceCompat
import java.util.*
import com.example.screenshot.Capture
class MainActivity : Activity() {
companion object {
private const val REQUEST_CAPTURE = 1
var projection: MediaProjection? = null
}
private lateinit var mediaProjectionManager: MediaProjectionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startServiceButton: Button = findViewById(R.id.start_service_button)
val stopServiceButton: Button = findViewById(R.id.stop_service_button)
mediaProjectionManager = getSystemService(Service.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent() , REQUEST_CAPTURE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val intent = Intent(this, CaptureService::class.java)
if (requestCode == REQUEST_CAPTURE) {
if (resultCode == RESULT_OK) {
.setAction(CaptureService.ACTION_ENABLE_CAPTURE)
startForegroundService(intent)
Thread.sleep(100)
projection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
Toast.makeText(this, "All OK", Toast.LENGTH_SHORT).show()
} else {
projection = null
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show()
}
}
//finish()
}
}
</code>
package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.ServiceCompat
import java.util.*
import com.example.screenshot.Capture
class MainActivity : Activity() {
companion object {
private const val REQUEST_CAPTURE = 1
var projection: MediaProjection? = null
}
private lateinit var mediaProjectionManager: MediaProjectionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startServiceButton: Button = findViewById(R.id.start_service_button)
val stopServiceButton: Button = findViewById(R.id.stop_service_button)
mediaProjectionManager = getSystemService(Service.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent() , REQUEST_CAPTURE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val intent = Intent(this, CaptureService::class.java)
if (requestCode == REQUEST_CAPTURE) {
if (resultCode == RESULT_OK) {
.setAction(CaptureService.ACTION_ENABLE_CAPTURE)
startForegroundService(intent)
Thread.sleep(100)
projection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
Toast.makeText(this, "All OK", Toast.LENGTH_SHORT).show()
} else {
projection = null
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show()
}
}
//finish()
}
}
<code>package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.widget.Toast
import androidx.core.app.ServiceCompat
import com.example.screenshot.Capture
class CaptureService : Service() {
private val TAG = CaptureService::class.qualifiedName
val ACTION_ENABLE_CAPTURE = "enable_capture"
private val notificationId = Random().nextInt()
private val capture = Capture(this)
override fun onBind(intent: Intent?): IBinder? {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
ACTION_ENABLE_CAPTURE -> onEnableCapture()
val channelName = "myChannel"
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
val notification: Notification =
Notification.Builder(applicationContext, channelId).setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher).setCategory(
Notification.CATEGORY_SERVICE
ServiceCompat.startForeground(this,1,notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
return Service.START_STICKY
private fun enableCapture() {
if (MainActivity.projection == null) {
Log.d(TAG, "startActivity(CaptureActivity)")
val intent = Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
private fun onEnableCapture() {
MainActivity.projection?.run {
private fun disableCapture() {
MainActivity.projection = null
override fun onDestroy() {
<code>package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.ServiceCompat
import java.util.*
import com.example.screenshot.Capture
class CaptureService : Service() {
companion object {
private val TAG = CaptureService::class.qualifiedName
val ACTION_ENABLE_CAPTURE = "enable_capture"
}
private val notificationId = Random().nextInt()
private val capture = Capture(this)
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
when (intent.action) {
ACTION_ENABLE_CAPTURE -> onEnableCapture()
}
}
val channelId = "001"
val channelName = "myChannel"
val channel =
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
val notification: Notification =
Notification.Builder(applicationContext, channelId).setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher).setCategory(
Notification.CATEGORY_SERVICE
).build()
ServiceCompat.startForeground(this,1,notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
return Service.START_STICKY
}
private fun enableCapture() {
if (MainActivity.projection == null) {
println("Phase1")
Log.d(TAG, "startActivity(CaptureActivity)")
val intent = Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else {
onEnableCapture()
}
}
private fun onEnableCapture() {
MainActivity.projection?.run {
capture.run(this) {
capture.stop()
// save bitmap
}
}
}
private fun disableCapture() {
capture.stop()
MainActivity.projection = null
}
override fun onDestroy() {
super.onDestroy()
disableCapture()
}
}
</code>
package com.example.screenshot
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
import android.graphics.Color
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.ServiceCompat
import java.util.*
import com.example.screenshot.Capture
class CaptureService : Service() {
companion object {
private val TAG = CaptureService::class.qualifiedName
val ACTION_ENABLE_CAPTURE = "enable_capture"
}
private val notificationId = Random().nextInt()
private val capture = Capture(this)
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
when (intent.action) {
ACTION_ENABLE_CAPTURE -> onEnableCapture()
}
}
val channelId = "001"
val channelName = "myChannel"
val channel =
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
val notification: Notification =
Notification.Builder(applicationContext, channelId).setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher).setCategory(
Notification.CATEGORY_SERVICE
).build()
ServiceCompat.startForeground(this,1,notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
return Service.START_STICKY
}
private fun enableCapture() {
if (MainActivity.projection == null) {
println("Phase1")
Log.d(TAG, "startActivity(CaptureActivity)")
val intent = Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else {
onEnableCapture()
}
}
private fun onEnableCapture() {
MainActivity.projection?.run {
capture.run(this) {
capture.stop()
// save bitmap
}
}
}
private fun disableCapture() {
capture.stop()
MainActivity.projection = null
}
override fun onDestroy() {
super.onDestroy()
disableCapture()
}
}
<code>package com.example.screenshot
import android.content.Context
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.ImageReader
import android.media.projection.MediaProjection
import android.widget.Toast
class Capture(val context: Context) : ImageReader.OnImageAvailableListener {
val TAG = Capture::class.qualifiedName
private var display: VirtualDisplay? = null
private var onCaptureListener: ((Bitmap) -> Unit)? = null
fun run(mediaProjection: MediaProjection, onCaptureListener: (Bitmap) -> Unit) {
this.onCaptureListener = onCaptureListener
display = createDisplay(mediaProjection)
private fun createDisplay(mediaProjection: MediaProjection): VirtualDisplay {
context.resources.displayMetrics.run {
val reader = ImageReader.newInstance(
widthPixels, heightPixels, PixelFormat.RGBA_8888, maxImages)
reader.setOnImageAvailableListener(this@Capture, null)
val display = mediaProjection.createVirtualDisplay(
"Capture Display", widthPixels, heightPixels, densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
reader.surface, null, null)
Log.d(TAG, "createVirtualDisplay")
override fun onImageAvailable(reader: ImageReader) {
onCaptureListener?.invoke(captureImage(reader))
private fun captureImage(reader: ImageReader): Bitmap {
val image = reader.acquireLatestImage()
context.resources.displayMetrics.run {
val bitmap = Bitmap.createBitmap(
rowStride / pixelStride, heightPixels, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(buffer)
<code>package com.example.screenshot
import android.content.Context
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.ImageReader
import android.media.projection.MediaProjection
import android.util.Log
import android.widget.Toast
class Capture(val context: Context) : ImageReader.OnImageAvailableListener {
companion object {
val TAG = Capture::class.qualifiedName
}
private var display: VirtualDisplay? = null
private var onCaptureListener: ((Bitmap) -> Unit)? = null
fun run(mediaProjection: MediaProjection, onCaptureListener: (Bitmap) -> Unit) {
this.onCaptureListener = onCaptureListener
if (display == null) {
display = createDisplay(mediaProjection)
}
}
private fun createDisplay(mediaProjection: MediaProjection): VirtualDisplay {
context.resources.displayMetrics.run {
val maxImages = 2
val reader = ImageReader.newInstance(
widthPixels, heightPixels, PixelFormat.RGBA_8888, maxImages)
reader.setOnImageAvailableListener(this@Capture, null)
val display = mediaProjection.createVirtualDisplay(
"Capture Display", widthPixels, heightPixels, densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
reader.surface, null, null)
Log.d(TAG, "createVirtualDisplay")
return display
}
}
override fun onImageAvailable(reader: ImageReader) {
if (display != null) {
onCaptureListener?.invoke(captureImage(reader))
}
}
private fun captureImage(reader: ImageReader): Bitmap {
val image = reader.acquireLatestImage()
context.resources.displayMetrics.run {
image.planes[0].run {
val bitmap = Bitmap.createBitmap(
rowStride / pixelStride, heightPixels, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(buffer)
image.close()
return bitmap
}
}
}
fun stop() {
display?.release()
display = null
onCaptureListener = null
}
}
</code>
package com.example.screenshot
import android.content.Context
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.ImageReader
import android.media.projection.MediaProjection
import android.util.Log
import android.widget.Toast
class Capture(val context: Context) : ImageReader.OnImageAvailableListener {
companion object {
val TAG = Capture::class.qualifiedName
}
private var display: VirtualDisplay? = null
private var onCaptureListener: ((Bitmap) -> Unit)? = null
fun run(mediaProjection: MediaProjection, onCaptureListener: (Bitmap) -> Unit) {
this.onCaptureListener = onCaptureListener
if (display == null) {
display = createDisplay(mediaProjection)
}
}
private fun createDisplay(mediaProjection: MediaProjection): VirtualDisplay {
context.resources.displayMetrics.run {
val maxImages = 2
val reader = ImageReader.newInstance(
widthPixels, heightPixels, PixelFormat.RGBA_8888, maxImages)
reader.setOnImageAvailableListener(this@Capture, null)
val display = mediaProjection.createVirtualDisplay(
"Capture Display", widthPixels, heightPixels, densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
reader.surface, null, null)
Log.d(TAG, "createVirtualDisplay")
return display
}
}
override fun onImageAvailable(reader: ImageReader) {
if (display != null) {
onCaptureListener?.invoke(captureImage(reader))
}
}
private fun captureImage(reader: ImageReader): Bitmap {
val image = reader.acquireLatestImage()
context.resources.displayMetrics.run {
image.planes[0].run {
val bitmap = Bitmap.createBitmap(
rowStride / pixelStride, heightPixels, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(buffer)
image.close()
return bitmap
}
}
}
fun stop() {
display?.release()
display = null
onCaptureListener = null
}
}
My AndroidManifest
<code><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ScreenShot"
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.ScreenShot">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
android:name=".CaptureService"
android:stopWithTask="true"
android:foregroundServiceType="mediaProjection" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<code><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ScreenShot"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ScreenShot">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".CaptureService"
android:enabled="true"
android:exported="true"
android:stopWithTask="true"
android:foregroundServiceType="mediaProjection" />
/>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
</manifest>
</code>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ScreenShot"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ScreenShot">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".CaptureService"
android:enabled="true"
android:exported="true"
android:stopWithTask="true"
android:foregroundServiceType="mediaProjection" />
/>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
</manifest>
Added permission to AndroidManifest – same problem
Added notifications to service – same problem