diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4228ea01..38a19d85 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -100,6 +100,18 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt
new file mode 100644
index 00000000..270370ac
--- /dev/null
+++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt
@@ -0,0 +1,193 @@
+package bums.lunatic.launcher.wall
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Paint
+import android.media.MediaCodec
+import android.media.MediaExtractor
+import android.media.MediaFormat
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicYuvToRGB
+import android.renderscript.Type
+import android.service.wallpaper.WallpaperService
+import android.view.SurfaceHolder
+import bums.lunatic.launcher.R
+
+
+class MyWallpaperService : WallpaperService() {
+ override fun onCreateEngine(): Engine {
+ return VideoEngine()
+ }
+
+ inner class VideoEngine : Engine() {
+ lateinit var holder: SurfaceHolder
+ private var renderThread: RenderThread? = null
+
+ override fun onCreate(surfaceHolder: SurfaceHolder) {
+ super.onCreate(surfaceHolder)
+ this.holder = surfaceHolder
+ }
+
+ override fun onSurfaceCreated(holder: SurfaceHolder) {
+ super.onSurfaceCreated(holder)
+ renderThread = RenderThread(holder, getApplicationContext())
+ renderThread?.start()
+ }
+
+ override fun onSurfaceDestroyed(holder: SurfaceHolder?) {
+ super.onSurfaceDestroyed(holder)
+ renderThread?.interrupt()
+ renderThread = null
+ }
+ }
+}
+class RenderThread(
+ private val holder: SurfaceHolder,
+ private val context: Context
+) : Thread() {
+
+ private var running = true
+
+ private var extractor: MediaExtractor? = null
+ private var codec: MediaCodec? = null
+ private var videoTrackIndex: Int = -1
+ private val bufferInfo = MediaCodec.BufferInfo()
+
+ private var rs: RenderScript? = null
+ private var yuvToRgb: ScriptIntrinsicYuvToRGB? = null
+
+ private var width = 0
+ private var height = 0
+
+ override fun run() {
+ try {
+ // 1. MediaExtractor 초기화
+ extractor = MediaExtractor().apply {
+ val afd = context.resources.openRawResourceFd(R.raw.sample)
+ setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
+ afd.close()
+ for (i in 0 until trackCount) {
+ val format = getTrackFormat(i)
+ val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
+ if (mime.startsWith("video/")) {
+ videoTrackIndex = i
+ selectTrack(i)
+ width = format.getInteger(MediaFormat.KEY_WIDTH)
+ height = format.getInteger(MediaFormat.KEY_HEIGHT)
+ break
+ }
+ }
+ }
+
+ if (videoTrackIndex < 0) {
+ // 비디오 트랙 없음 종료
+ return
+ }
+
+ // 2. MediaCodec 초기화 (Surface가 아닌 직접 버퍼 받을 것)
+ extractor?.getTrackFormat(videoTrackIndex)?.let { format ->
+ codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!).apply {
+ configure(format, null, null, 0)
+ start()
+ }
+ }
+
+ // 3. RenderScript 초기화
+ rs = RenderScript.create(context)
+ yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
+
+ var isEOS = false
+ while (running && !isInterrupted) {
+ if (!isEOS) {
+ val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1
+ if (inputBufferIndex >= 0) {
+ val inputBuffer = codec?.getInputBuffer(inputBufferIndex)
+ if (inputBuffer != null) {
+ val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1
+ if (sampleSize < 0) {
+ codec?.queueInputBuffer(
+ inputBufferIndex,
+ 0,
+ 0,
+ 0,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM
+ )
+ isEOS = true
+ } else {
+ val presentationTimeUs = extractor?.sampleTime ?: 0L
+ codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0)
+ extractor?.advance()
+ }
+ }
+ }
+ }
+
+ val outputBufferIndex = codec?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1
+ if (outputBufferIndex >= 0) {
+ val outputBuffer = codec?.getOutputBuffer(outputBufferIndex)
+ if (bufferInfo.size > 0 && outputBuffer != null) {
+ val yuvData = ByteArray(bufferInfo.size)
+ outputBuffer.get(yuvData)
+
+ // 4. RenderScript로 YUV → Bitmap 변환
+ val bitmap = convertYUVToBitmap(yuvData, width, height)
+
+ // 5. Canvas에 그리기
+ val canvas = holder.lockCanvas()
+ if (canvas != null) {
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
+
+ // 오버레이 예: 텍스트 그리기
+ val paint = Paint().apply {
+ color = Color.WHITE
+ textSize = 40f
+ }
+ canvas.drawText("Live Wallpaper Overlay", 50f, 50f, paint)
+
+ holder.unlockCanvasAndPost(canvas)
+ }
+
+ outputBuffer.clear()
+ }
+ codec?.releaseOutputBuffer(outputBufferIndex, false)
+ } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // format 변경 시 처리할 것 (필요시)
+ }
+
+ // 프레임 타이밍 조절 (예: 30fps 제한)
+ sleep(33)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ } finally {
+ // 리소스 해제
+ codec?.stop()
+ codec?.release()
+ extractor?.release()
+ rs?.destroy()
+ }
+ }
+
+ private fun convertYUVToBitmap(yuvByteArray: ByteArray, width: Int, height: Int): Bitmap {
+ val yuvType = Type.Builder(rs, Element.U8(rs)).setX(yuvByteArray.size)
+ val inAllocation = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT)
+
+ val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height)
+ val outAllocation = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT)
+
+ inAllocation.copyFrom(yuvByteArray)
+ yuvToRgb?.setInput(inAllocation)
+ yuvToRgb?.forEach(outAllocation)
+
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ outAllocation.copyTo(bitmap)
+
+ inAllocation.destroy()
+ outAllocation.destroy()
+
+ return bitmap
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/sample.mp4 b/app/src/main/res/raw/sample.mp4
new file mode 100644
index 00000000..f9f3c6c3
Binary files /dev/null and b/app/src/main/res/raw/sample.mp4 differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index aa88d4a1..9d7c59d1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -178,4 +178,5 @@
차량 블루투스 이름
+ test\n
diff --git a/app/src/main/res/xml/wallpaper.xml b/app/src/main/res/xml/wallpaper.xml
new file mode 100644
index 00000000..337e2739
--- /dev/null
+++ b/app/src/main/res/xml/wallpaper.xml
@@ -0,0 +1,6 @@
+
+