Model overlay window IPC and foreground service lifetime for Android host tests
This commit is contained in:
1 parent
6d5b2bd176
commit
d1dc3bb8f7
3 files changed
+117
-1
No files matched your search
@@ -0,0 +1,31 @@
|
||||
package android.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
/** OS lifecycle контролирует тест: stopSelf лишь запрашивает stop, onDestroy доставляется отдельно. */
|
||||
open class Service : Context() {
|
||||
companion object { const val START_NOT_STICKY = 2; const val STOP_FOREGROUND_REMOVE = 1 }
|
||||
var foregroundId = 0
|
||||
var foregroundTypes = 0
|
||||
var foregroundAttempts = 0
|
||||
var failForeground = false
|
||||
var failForegroundAfter = false
|
||||
var stops = 0
|
||||
var destroys = 0
|
||||
open fun onCreate() {}
|
||||
open fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_NOT_STICKY
|
||||
open fun onDestroy() { destroys++ }
|
||||
open fun onBind(intent: Intent?): IBinder? = null
|
||||
fun startForeground(id: Int, notification: Notification) {
|
||||
foregroundAttempts++
|
||||
check(!failForeground) { "foreground promotion unavailable" }
|
||||
foregroundId = id
|
||||
requireNotNull(notificationManager).notify(id, notification)
|
||||
check(!failForegroundAfter) { "foreground promotion failed after effect" }
|
||||
}
|
||||
fun stopForeground(flags: Int) { if (flags == STOP_FOREGROUND_REMOVE && foregroundId != 0) notificationManager?.cancel(foregroundId) }
|
||||
fun stopSelf() { stops++ }
|
||||
fun stopSelf(startId: Int) { stops++ }
|
||||
}
|
||||
@@ -13,11 +13,12 @@ import com.utun.chat.data.HostConfigProvider
|
||||
import java.io.File
|
||||
|
||||
/** Только host-тест: управляемые Android managers, без устройств/разрешений ОС. */
|
||||
class Context {
|
||||
open class Context {
|
||||
companion object {
|
||||
const val AUDIO_SERVICE = "audio"; const val SENSOR_SERVICE = "sensor"; const val VIBRATOR_SERVICE = "vibrator"
|
||||
const val POWER_SERVICE = "power"
|
||||
const val WIFI_SERVICE = "wifi"
|
||||
const val WINDOW_SERVICE = "window"
|
||||
const val RECEIVER_NOT_EXPORTED = 4
|
||||
}
|
||||
val packageName = "com.utun.chat"
|
||||
@@ -26,6 +27,22 @@ class Context {
|
||||
var vibrator: Vibrator? = Vibrator()
|
||||
var powerManager: PowerManager? = PowerManager()
|
||||
var failPowerService = false
|
||||
val resources = HostResources()
|
||||
var windowManager: android.view.WindowManager? = android.view.WindowManager()
|
||||
val serviceStarts = mutableListOf<Intent>()
|
||||
val serviceStops = mutableListOf<Intent>()
|
||||
var failServiceStart = false
|
||||
var failServiceStop = false
|
||||
var acceptServiceStop = true
|
||||
fun startForegroundService(intent: Intent) {
|
||||
serviceStarts.add(Intent(intent))
|
||||
check(!failServiceStart) { "foreground service start unavailable" }
|
||||
}
|
||||
fun stopService(intent: Intent): Boolean {
|
||||
serviceStops.add(Intent(intent))
|
||||
check(!failServiceStop) { "service stop unavailable" }
|
||||
return acceptServiceStop
|
||||
}
|
||||
val activityAttempts = mutableListOf<Intent>()
|
||||
var onStartActivity: (Intent) -> Unit = {}
|
||||
var notificationManager: NotificationManager? = NotificationManager()
|
||||
@@ -64,6 +81,7 @@ class Context {
|
||||
VIBRATOR_SERVICE -> vibrator
|
||||
POWER_SERVICE -> { check(!failPowerService) { "power service lookup unavailable" }; powerManager }
|
||||
WIFI_SERVICE -> { check(!failWifiService) { "Wi-Fi service unavailable" }; wifiManager }
|
||||
WINDOW_SERVICE -> windowManager
|
||||
else -> error("Unknown service: $name")
|
||||
}
|
||||
fun <T> getSystemService(type: Class<T>): T? {
|
||||
@@ -79,3 +97,9 @@ class Context {
|
||||
}
|
||||
fun checkSelfPermission(name: String): Int = if (permissionGranted) 0 else -1
|
||||
}
|
||||
|
||||
class HostResources {
|
||||
val displayMetrics = HostDisplayMetrics()
|
||||
fun getResourceEntryName(id: Int) = "resource_$id"
|
||||
}
|
||||
class HostDisplayMetrics { var density = 1f; var widthPixels = 1080 }
|
||||
@@ -0,0 +1,61 @@
|
||||
package android.view
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/** Модель оконного IPC: add/remove могут отказать до или после эффекта; callbacks доставляются вручную. */
|
||||
class WindowManager {
|
||||
class LayoutParams(var width: Int, var height: Int, val type: Int, val flags: Int, val format: Int) {
|
||||
companion object {
|
||||
const val WRAP_CONTENT = -2; const val TYPE_APPLICATION_OVERLAY = 2038; const val TYPE_PHONE = 2002
|
||||
const val FLAG_NOT_FOCUSABLE = 8
|
||||
}
|
||||
var gravity = 0; var x = 0; var y = 0
|
||||
}
|
||||
val windows = mutableMapOf<View, LayoutParams>()
|
||||
var failures = emptySet<String>()
|
||||
var additions = 0; var removals = 0; var updates = 0
|
||||
fun addView(view: View, params: LayoutParams) {
|
||||
additions++
|
||||
check("add" !in failures) { "window add unavailable" }
|
||||
windows[view] = params; view.layout = params
|
||||
check("add after" !in failures) { "window add failed after effect" }
|
||||
}
|
||||
fun removeView(view: View) {
|
||||
removals++
|
||||
check("remove" !in failures) { "window remove unavailable" }
|
||||
require(windows.remove(view) != null) { "window not attached" }
|
||||
check("remove after" !in failures) { "window remove failed after effect" }
|
||||
}
|
||||
fun updateViewLayout(view: View, params: LayoutParams) {
|
||||
updates++
|
||||
check("update" !in failures) { "window layout unavailable" }
|
||||
require(windows.containsKey(view)) { "window not attached" }
|
||||
windows[view] = params; view.layout = params
|
||||
}
|
||||
}
|
||||
open class View {
|
||||
fun interface OnTouchListener { fun onTouch(view: View, event: MotionEvent): Boolean }
|
||||
var background: Any? = null
|
||||
var layout: WindowManager.LayoutParams? = null
|
||||
var isLaidOut = true
|
||||
val width get() = layout?.width ?: 0
|
||||
val height get() = layout?.height ?: 0
|
||||
var listener: OnTouchListener? = null
|
||||
val animations = mutableListOf<Runnable>()
|
||||
val layouts = mutableListOf<() -> Unit>()
|
||||
fun setOnTouchListener(value: OnTouchListener) { listener = value }
|
||||
fun touch(event: MotionEvent) = listener?.onTouch(this, event) ?: false
|
||||
fun postOnAnimation(task: Runnable) { animations.add(task) }
|
||||
fun dispatchAnimations() { val pending = animations.toList(); animations.clear(); pending.forEach { it.run() } }
|
||||
fun getLocationOnScreen(out: IntArray) { out[0] = layout?.x ?: 0; out[1] = layout?.y ?: 0 }
|
||||
}
|
||||
class MotionEvent(val action: Int, val rawX: Float = 0f, val rawY: Float = 0f, val eventTime: Long = 1,
|
||||
val downTime: Long = eventTime) {
|
||||
companion object { const val ACTION_DOWN = 0; const val ACTION_UP = 1; const val ACTION_MOVE = 2; const val ACTION_CANCEL = 3 }
|
||||
}
|
||||
object Gravity { const val CENTER = 17; const val TOP = 48; const val LEFT = 3 }
|
||||
class ViewConfiguration {
|
||||
val scaledTouchSlop = 8
|
||||
companion object { fun get(context: Context) = ViewConfiguration(); fun getDoubleTapTimeout() = 300 }
|
||||
}
|
||||
class LayoutInflater
|
||||
Reference in new issue
Block a user