Android启动应用到虚拟屏和将应用在默认屏和虚拟屏之间进行移动。都在代码里了:
package com.tinnove.aiagent.base
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.ActivityOptions
import android.content.Context
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.ImageReader
import android.os.Build
import android.util.Log
import android.view.Display
import java.lang.reflect.Method
/**
* Description: AppDisplayMover
* 该类用于:
* 1)启动 App进程 到虚拟屏
* 2)将 App进程 从默认屏 move 到虚拟屏
* 3)将 App进程 从虚拟屏 move 到默认屏
*
* Author: platolei
* Date: 2025/4/4 17:00
*/
object AppDisplayMover {
private const val TAG = "AppDisplayMover"
private const val DISPLAY_NAME = "AgentVirtualDisplay"
@JvmField
var virtualDisplayId = -1
private var mDisplayManager: DisplayManager? = null
private var mVirtualDisplay: VirtualDisplay? = null
private var mImageReader: ImageReader? = null
/**
* 启动 App进程 到虚拟屏
*
* @param context
* @param packageName
*/
@JvmStatic
fun launchAppByPackage(context: Context, packageName: String?) {
val packageManager = context.packageManager
val launchIntent = packageManager.getLaunchIntentForPackage(packageName!!)
if (launchIntent != null) {
if (virtualDisplayId == -1) {
Log.e(TAG, "未初始化")
throw RuntimeException("AppDisplayMover:未初始化")
}
val options = ActivityOptions.makeBasic()
options.launchDisplayId = virtualDisplayId
context.startActivity(launchIntent, options.toBundle())
} else {
Log.e(TAG, "未找到该应用,请检查包名")
}
}
/**
* 初始化虚拟屏
*
* @param context
*/
@JvmStatic
fun init(context: Context) {
// 获取 DisplayManager 服务
mDisplayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val widthPixels = context.resources.displayMetrics.widthPixels
val heightPixels = context.resources.displayMetrics.heightPixels
// 创建 ImageReader 用于提供 Surface
mImageReader = ImageReader.newInstance(
widthPixels,
heightPixels,
PixelFormat.RGBA_8888,
2
)
// 创建虚拟显示屏
mVirtualDisplay = mDisplayManager!!.createVirtualDisplay(
DISPLAY_NAME,
widthPixels,
heightPixels,
context.resources.displayMetrics.densityDpi,
mImageReader!!.surface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
)
if (mVirtualDisplay != null) {
// 获取虚拟屏幕的 Display ID
virtualDisplayId = mVirtualDisplay!!.display.displayId
Log.i(TAG, "虚拟屏幕创建成功,virtualDisplayId:$virtualDisplayId")
}
}
/**
* 将 App进程 从虚拟屏 move 到默认屏
*
* @param context 上下文
* @param packageName 目标应用包名
*/
@JvmStatic
fun moveApp2DefaultDisplay(context: Context, packageName: String): Boolean {
// 获取目标应用的任务 ID
val taskId = getTaskIdByPackageName(context, packageName)
if (taskId == -1) {
Log.i(TAG, "未找到包名为 $packageName 的任务")
return false
}
// 迁移任务到默认屏幕
kotlin.runCatching {
moveTaskToDisplay(taskId, getDefaultDisplayId(context))
return true
}.onFailure {
Log.e(TAG, it.message.toString())
}
return false
}
/**
* 将 App进程 从默认屏 move 到虚拟屏
*
* @param context 上下文
* @param packageName 目标应用包名
*/
@JvmStatic
fun moveApp2VirtualDisplay(context: Context, packageName: String): Boolean {
// 获取目标应用的任务 ID
val taskId = getTaskIdByPackageName(context, packageName)
if (taskId == -1) {
Log.i(TAG, "未找到包名为 $packageName 的任务")
return false
}
// 迁移任务到默认屏幕
kotlin.runCatching {
moveTaskToDisplay(taskId, virtualDisplayId)
return true
}.onFailure {
Log.e(TAG, it.message.toString())
}
return false
}
/**
* 获取默认屏幕的 Display ID
*
* @param context
* @return
*/
private fun getDefaultDisplayId(context: Context): Int {
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
val defaultDisplayId = defaultDisplay?.displayId ?: -1
Log.i(TAG, "defaultDisplayId:$defaultDisplayId")
return defaultDisplayId
}
/**
* 通过包名获取前台任务 ID
*
* @param context
* @param packageName
* @return
*/
private fun getTaskIdByPackageName(context: Context, packageName: String): Int {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// Android 10+ 需使用新 API
val tasks = am.getRunningTasks(100)
for (task in tasks) {
if (task.topActivity != null && task.topActivity!!.packageName == packageName) {
return task.taskId
}
}
return -1
}
/**
* move 任务到指定屏幕
*
* @param taskId
* @param displayId
*/
private fun moveTaskToDisplay(taskId: Int, displayId: Int) {
val activityTaskManagerService = getActivityTaskManagerService()
activityTaskManagerService?.let {
val clazz: Class<*> = activityTaskManagerService.javaClass
val moveTaskToDisplayMethod: Method =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
clazz.getMethod(
"moveRootTaskToDisplay",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType
)
} else {
clazz.getMethod(
"moveStackToDisplay",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType
)
}
moveTaskToDisplayMethod.isAccessible = true
moveTaskToDisplayMethod.invoke(activityTaskManagerService, taskId, displayId)
}
}
/**
* 通过反射获取 ActivityTaskManagerService
*
* @return ActivityTaskManagerService
*/
@SuppressLint("PrivateApi")
private fun getActivityTaskManagerService(): Any? {
val clazz = Class.forName("android.app.ActivityTaskManager")
val getServiceMethod = clazz.getMethod("getService")
getServiceMethod.isAccessible = true
return getServiceMethod.invoke(null)
}
/**
* 释放虚拟屏
*/
@JvmStatic
fun release() {
if (mVirtualDisplay != null) {
mVirtualDisplay!!.release()
mVirtualDisplay = null
}
if (mImageReader != null) {
mImageReader!!.close()
mImageReader = null
}
}
}