...
This commit is contained in:
@@ -1,19 +1,27 @@
|
||||
package bums.lunatic.launcher.wall
|
||||
|
||||
import android.app.WallpaperManager
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.os.BatteryManager
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.service.wallpaper.WallpaperService
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import androidx.work.ListenableWorker
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.LocationUpdateService
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class MyWallpaperService : WallpaperService() {
|
||||
|
||||
@@ -25,6 +33,18 @@ class MyWallpaperService : WallpaperService() {
|
||||
Log.d(TAG, "onCreateEngine: New engine instance created.")
|
||||
return NativeRenderEngine()
|
||||
}
|
||||
private fun getBatteryStatus(): Pair<Int, Boolean> {
|
||||
val intent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
|
||||
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
|
||||
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
|
||||
|
||||
val batteryPct = (level / scale.toFloat() * 100).toInt()
|
||||
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL
|
||||
|
||||
return Pair(batteryPct, isCharging)
|
||||
}
|
||||
|
||||
inner class NativeRenderEngine : Engine() {
|
||||
private lateinit var handlerThread: HandlerThread
|
||||
@@ -136,6 +156,16 @@ class MyWallpaperService : WallpaperService() {
|
||||
initializeRenderer()
|
||||
}
|
||||
requestSyncRenderState()
|
||||
|
||||
if (visible) {
|
||||
// 화면이 켜지면 즉시 업데이트하고 타이머 시작
|
||||
handler.removeCallbacks(updateWeatherRunnable)
|
||||
handler.post(updateWeatherRunnable)
|
||||
} else {
|
||||
// 화면이 꺼지면 타이머 중지
|
||||
handler.removeCallbacks(updateWeatherRunnable)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- ⬇️ 상태 동기화를 요청하는 함수 추가 ⬇️ ---
|
||||
@@ -194,12 +224,18 @@ class MyWallpaperService : WallpaperService() {
|
||||
nativeRenderer?.setAnimationMode(NativeRenderer.ANIMATION_MODE_PAN)
|
||||
nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_FADE)
|
||||
nativeRenderer?.setAnimationSpeed(1.0f)
|
||||
nativeRenderer?.setImageRenderFrame(70)
|
||||
NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback)
|
||||
|
||||
|
||||
if (mediaFiles.isEmpty()) {
|
||||
handler.post { loadMediaFiles() }
|
||||
}
|
||||
|
||||
handler.removeCallbacks(updateWeatherRunnable)
|
||||
// 즉시 첫 번째 비트맵 생성 및 전송
|
||||
handler.post(updateWeatherRunnable)
|
||||
|
||||
}
|
||||
|
||||
val requiredSizeRatio = 0.5
|
||||
@@ -243,6 +279,110 @@ class MyWallpaperService : WallpaperService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawBitmapFromText(weatherLines: List<String>, isCharging: Boolean = false): Bitmap? {
|
||||
val dateFull = SimpleDateFormat("yyyy.MM.dd", Locale.KOREAN).format(Date())
|
||||
// val timeFull = SimpleDateFormat(if (isCharging) "HH:mm:ss" else "HH:mm", Locale.KOREAN).format(Date())
|
||||
|
||||
val finalLines = mutableListOf<String>()
|
||||
if (weatherLines.isNotEmpty()) finalLines.add(weatherLines[0]) // Index 0: 날씨
|
||||
|
||||
// finalLines.add(timeFull) // Index 2: 시간
|
||||
if (weatherLines.size > 1) {
|
||||
for (i in 1 until weatherLines.size - 1) finalLines.add(weatherLines[i])
|
||||
finalLines.add(weatherLines.last()) // 마지막: 주소
|
||||
}
|
||||
finalLines.add(dateFull) // Index 1: 날짜
|
||||
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
textAlign = Paint.Align.CENTER
|
||||
typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL)
|
||||
setShadowLayer(10f, 3f, 3f, Color.BLACK)
|
||||
}
|
||||
|
||||
val titleSize = 80f
|
||||
val dateSize = 50f
|
||||
val addressSize = 30f
|
||||
val lineSpacingMultiplier = 0.2f // 폰트 크기의 20%를 순수 여백으로 사용
|
||||
|
||||
// --- 4. 크기 측정 및 라인별 높이 저장 ---
|
||||
var maxWidth = 0f
|
||||
var totalHeight = 40f // 상단 여백
|
||||
val lineHeights = mutableListOf<Float>() // 각 줄이 차지하는 총 높이
|
||||
val lineBaselines = mutableListOf<Float>() // 각 줄의 글자가 그려질 Baseline 위치
|
||||
|
||||
finalLines.forEachIndexed { index, line ->
|
||||
paint.textSize = when (index) {
|
||||
0 -> titleSize
|
||||
2 -> addressSize
|
||||
else -> dateSize
|
||||
}
|
||||
|
||||
val width = paint.measureText(line)
|
||||
if (width > maxWidth) maxWidth = width
|
||||
|
||||
val metrics = paint.fontMetrics
|
||||
val fontHeight = metrics.descent - metrics.ascent // 실제 글자 높이
|
||||
val leading = paint.textSize * lineSpacingMultiplier // 동적 행간
|
||||
|
||||
val fullLineHeight = fontHeight + leading
|
||||
|
||||
// 현재 줄의 Baseline 계산: 이전까지의 높이 + 글자의 ascent 절대값
|
||||
lineBaselines.add(totalHeight - metrics.ascent)
|
||||
totalHeight += fullLineHeight
|
||||
lineHeights.add(fullLineHeight)
|
||||
}
|
||||
totalHeight += 20f // 하단 여백
|
||||
|
||||
// --- 5. 비트맵 생성 및 그리기 ---
|
||||
val bitmap = Bitmap.createBitmap((maxWidth + 100).toInt(), totalHeight.toInt(), Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
val centerX = bitmap.width / 2f
|
||||
|
||||
finalLines.forEachIndexed { index, line ->
|
||||
paint.textSize = when (index) {
|
||||
0 -> titleSize
|
||||
2 -> addressSize
|
||||
else -> dateSize
|
||||
}
|
||||
// 미리 계산된 Baseline에 그리기만 하면 끝!
|
||||
canvas.drawText(line, centerX, lineBaselines[index], paint)
|
||||
}
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private val updateWeatherRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
// if (!isVisible) return
|
||||
//
|
||||
// val (batteryLevel, isCharging) = getBatteryStatus()
|
||||
//
|
||||
// // --- ⬇️ 제안하신 조건별 로직 적용 ⬇️ ---
|
||||
// var nextDelay = 30000L // 기본 20초
|
||||
//
|
||||
// if (isCharging) {
|
||||
// nextDelay = 1000L // 충전 중이거나 70% 이상이면 10초
|
||||
// nativeRenderer?.setImageRenderFrame(45)
|
||||
// } else if (batteryLevel >= 80) {
|
||||
// nativeRenderer?.setImageRenderFrame(70)
|
||||
// } else {
|
||||
// handler.removeCallbacks(this)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 비트맵 생성 및 전달
|
||||
// val weather = LocationUpdateService.lastWeather
|
||||
// val bitmap = drawBitmapFromText(weather,isCharging)
|
||||
// bitmap?.let {
|
||||
// nativeRenderer?.setWeatherBitmap(it)
|
||||
// }
|
||||
//
|
||||
// Log.d(TAG, "Next update in ${nextDelay / 1000}s (Battery: $batteryLevel%, Charging: $isCharging)")
|
||||
// handler.postDelayed(this, nextDelay)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMediaFiles() {
|
||||
loadFiles()
|
||||
if (mediaFiles.isNotEmpty()) {
|
||||
@@ -253,6 +393,7 @@ class MyWallpaperService : WallpaperService() {
|
||||
} ?: run {
|
||||
Log.e(TAG, "Failed to get fd for initial media: ${initialFile.absolutePath}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val mediaDir = File(File(this@MyWallpaperService.getExternalFilesDir(null), "completed_torrents"), "Images")
|
||||
@@ -269,6 +410,20 @@ class MyWallpaperService : WallpaperService() {
|
||||
} ?: run {
|
||||
Log.e(TAG, "Callback: Failed to get fd for ${nextFile.absolutePath}")
|
||||
}
|
||||
|
||||
val (batteryLevel, isCharging) = getBatteryStatus()
|
||||
|
||||
if (batteryLevel >= 70) {
|
||||
val bitmap = drawBitmapFromText(LocationUpdateService.lastWeather)
|
||||
bitmap?.let { nativeRenderer?.setWeatherBitmap(it) }
|
||||
Log.d(TAG, "Battery Low: Weather updated only on media change.")
|
||||
} else {
|
||||
nativeRenderer?.setImageRenderFrame(99)
|
||||
nativeRenderer?.setWeatherBitmap(null)
|
||||
}
|
||||
if (isCharging || batteryLevel > 90) {
|
||||
// handler.post(updateWeatherRunnable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package bums.lunatic.launcher.wall
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
|
||||
@@ -29,7 +30,11 @@ open class NativeRenderer {
|
||||
nativeHandle = 0L
|
||||
}
|
||||
}
|
||||
|
||||
fun setWeatherBitmap(bitmap: Bitmap?) {
|
||||
if (nativeHandle != 0L) {
|
||||
nativeSetWeatherBitmap(nativeHandle, bitmap)
|
||||
}
|
||||
}
|
||||
fun render(surface: Surface) {
|
||||
if (nativeHandle != 0L) {
|
||||
nativeRender(nativeHandle, surface)
|
||||
@@ -96,7 +101,13 @@ open class NativeRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun setImageRenderFrame(frame:Int) {
|
||||
if (nativeHandle != 0L) {
|
||||
nativeSetImageRenderFrame(nativeHandle, frame)
|
||||
} else {
|
||||
"Kotlin Wrapper: Native handle is null."
|
||||
}
|
||||
}
|
||||
|
||||
// --- Private JNI declarations ---
|
||||
private external fun nativeStartRenderLoop(nativeHandle: Long, surface: Surface)
|
||||
@@ -104,7 +115,9 @@ open class NativeRenderer {
|
||||
private external fun nativeInit(): Long
|
||||
private external fun nativeDestroy(nativeHandle: Long)
|
||||
private external fun nativeRender(nativeHandle: Long, surface: Surface)
|
||||
private external fun nativeSetWeatherBitmap(nativeHandle: Long, bitmap: Bitmap?)
|
||||
private external fun nativeStartNextPreload(nativeHandle: Long, fd: Int)
|
||||
private external fun nativeSetImageRenderFrame(nativeHandle: Long, frame: Int)
|
||||
private external fun nativeSetAnimationSpeed(nativeHandle: Long, speed: Float)
|
||||
private external fun nativeSetAnimationMode(nativeHandle: Long, mode: Int)
|
||||
private external fun nativeSetFadeDuration(nativeHandle: Long, duration: Int)
|
||||
|
||||
@@ -41,6 +41,7 @@ class LocationUpdateService : Service(), LocationListener {
|
||||
|
||||
companion object {
|
||||
var longitude: Double = 0.0
|
||||
var lastWeather = mutableListOf<String>()
|
||||
var latitude: Double = 0.0
|
||||
fun pushLocation(context: Context, lat :Double, long : Double) {
|
||||
try {
|
||||
@@ -91,7 +92,7 @@ class LocationUpdateService : Service(), LocationListener {
|
||||
// 응답 받아서 처리
|
||||
val body: ResponseBody? = response.body
|
||||
if (body != null) {
|
||||
// Blog.LOGE("Location >>> ${body.string()}")
|
||||
Blog.LOGE("Location >>> ${body.string()}")
|
||||
}
|
||||
} else Blog.LOGE("telegram Error Occurred")
|
||||
}
|
||||
@@ -99,6 +100,41 @@ class LocationUpdateService : Service(), LocationListener {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS)
|
||||
|
||||
Blog.LOGE("addresses ${addresses.first()}")
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
try {
|
||||
val url = "https://api.weatherapi.com/v1/current.json?key=${PrefString.weatherApiKey.get()}&q=${lat},${long}&aqi=no"
|
||||
if (url.length > 10) {
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectionPool(ConnectionPool(5, 60, TimeUnit.SECONDS))
|
||||
.build()
|
||||
|
||||
// GET 요청 객체 생성
|
||||
val builder: Request.Builder = Request.Builder().url(url).get()
|
||||
val request: Request = builder.build()
|
||||
|
||||
// Blog.LOGE("telegram before request ")
|
||||
// OkHttp 클라이언트로 GET 요청 객체 전송
|
||||
val response: Response = client.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
// 응답 받아서 처리
|
||||
val body: ResponseBody? = response.body
|
||||
if (body != null) {
|
||||
var result = body.string()
|
||||
var w = Gson().fromJson<CurrentWeather>(result,CurrentWeather::class.java)
|
||||
w.addr = addresses.first().getAddressLine(0).replace("대한민국", "")
|
||||
lastWeather.clear()
|
||||
lastWeather.addAll(w.getSummaryInfo())
|
||||
Blog.LOGE("Location >>> ${result}\n${lastWeather}")
|
||||
}
|
||||
} else Blog.LOGE("telegram Error Occurred")
|
||||
}
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS)
|
||||
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -174,9 +210,63 @@ class LocationUpdateService : Service(), LocationListener {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class Condition {
|
||||
var text: String? = null
|
||||
var icon: String? = null
|
||||
var code: Int = 0
|
||||
}
|
||||
|
||||
open class Current {
|
||||
var last_updated_epoch: Int = 0
|
||||
var last_updated: String? = null
|
||||
var temp_c: Double = 0.0
|
||||
var temp_f: Double = 0.0
|
||||
var is_day: Int = 0
|
||||
var condition: Condition? = null
|
||||
var wind_mph: Double = 0.0
|
||||
var wind_kph: Double = 0.0
|
||||
var wind_degree: Int = 0
|
||||
var wind_dir: String? = null
|
||||
var pressure_mb: Double = 0.0
|
||||
var pressure_in: Double = 0.0
|
||||
var precip_mm: Double = 0.0
|
||||
var precip_in: Double = 0.0
|
||||
var humidity: Int = 0
|
||||
var cloud: Int = 0
|
||||
var feelslike_c: Double = 0.0
|
||||
var feelslike_f: Double = 0.0
|
||||
var windchill_c: Double = 0.0
|
||||
var windchill_f: Double = 0.0
|
||||
var heatindex_c: Double = 0.0
|
||||
var heatindex_f: Double = 0.0
|
||||
var dewpoint_c: Double = 0.0
|
||||
var dewpoint_f: Double = 0.0
|
||||
var vis_km: Double = 0.0
|
||||
var vis_miles: Double = 0.0
|
||||
var uv: Double = 0.0
|
||||
var gust_mph: Double = 0.0
|
||||
var gust_kph: Double = 0.0
|
||||
}
|
||||
|
||||
open class Location {
|
||||
var name: String? = null
|
||||
var region: String? = null
|
||||
var country: String? = null
|
||||
var lat: Double = 0.0
|
||||
var lon: Double = 0.0
|
||||
var tz_id: String? = null
|
||||
var localtime_epoch: Int = 0
|
||||
var localtime: String? = null
|
||||
}
|
||||
|
||||
open class CurrentWeather {
|
||||
var addr: String? = null
|
||||
var current: Current? = null
|
||||
|
||||
fun getSummaryInfo() = listOf<String>("${this.current?.condition?.text}",
|
||||
"${this.current?.temp_c}℃ ${this.current?.humidity}%"
|
||||
,"${addr}")
|
||||
}
|
||||
Reference in New Issue
Block a user