...
This commit is contained in:
parent
5a8e2293ca
commit
92d228d2e0
@ -54,7 +54,7 @@ target_link_libraries(native_renderer
|
|||||||
avutil
|
avutil
|
||||||
swscale
|
swscale
|
||||||
swresample
|
swresample
|
||||||
|
jnigraphics
|
||||||
# 안드로이드 기본 라이브러리들
|
# 안드로이드 기본 라이브러리들
|
||||||
${log-lib}
|
${log-lib}
|
||||||
${android-lib}
|
${android-lib}
|
||||||
|
|||||||
@ -237,6 +237,26 @@ void Renderer::handleTransitionState(ANativeWindow_Buffer& buffer, int surfaceWi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::updateWeatherBitmap(void* pixels, int width, int height) {
|
||||||
|
std::lock_guard<std::mutex> lock(weatherMutex_);
|
||||||
|
weatherWidth_ = width;
|
||||||
|
weatherHeight_ = height;
|
||||||
|
|
||||||
|
size_t size = static_cast<size_t>(width * height * 4);
|
||||||
|
weatherPixels_.assign(static_cast<uint8_t*>(pixels), static_cast<uint8_t*>(pixels) + size);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::clearWeatherBitmap() {
|
||||||
|
std::lock_guard<std::mutex> lock(weatherMutex_);
|
||||||
|
weatherPixels_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::setImageRenderFrame(int frame) {
|
||||||
|
std::lock_guard<std::mutex> lock(weatherMutex_);
|
||||||
|
imageFrame_ = frame;
|
||||||
|
}
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// 메인 렌더링 루프 (교통정리 담당)
|
// 메인 렌더링 루프 (교통정리 담당)
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
@ -252,7 +272,7 @@ void Renderer::renderFrame(ANativeWindow* window) {
|
|||||||
currentFrameDelay_ = std::chrono::milliseconds(static_cast<long long>(1000.0 / currentMedia_.getFps()));
|
currentFrameDelay_ = std::chrono::milliseconds(static_cast<long long>(1000.0 / currentMedia_.getFps()));
|
||||||
LOGI("Media type: Video. Frame delay set to %lldms for %.2f FPS", currentFrameDelay_.count(), currentMedia_.getFps());
|
LOGI("Media type: Video. Frame delay set to %lldms for %.2f FPS", currentFrameDelay_.count(), currentMedia_.getFps());
|
||||||
} else {
|
} else {
|
||||||
currentFrameDelay_ = std::chrono::milliseconds(70); // 이미지일 경우 30fps
|
currentFrameDelay_ = std::chrono::milliseconds(imageFrame_); // 이미지일 경우 30fps
|
||||||
LOGI("Media type: Image. Frame delay set to 33ms (~30 FPS)");
|
LOGI("Media type: Image. Frame delay set to 33ms (~30 FPS)");
|
||||||
}
|
}
|
||||||
currentState_ = RenderState::ANIMATING;
|
currentState_ = RenderState::ANIMATING;
|
||||||
@ -288,6 +308,65 @@ void Renderer::renderFrame(ANativeWindow* window) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(weatherMutex_);
|
||||||
|
if (!weatherPixels_.empty()) {
|
||||||
|
// 1. 위치 업데이트 (픽셀 단위 이동)
|
||||||
|
weatherX_ += weatherVelX_;
|
||||||
|
weatherY_ += weatherVelY_;
|
||||||
|
|
||||||
|
// 2. 화면 경계 검사 및 튕기기 (Bounce)
|
||||||
|
// 가로 경계
|
||||||
|
if (weatherX_ < 0) {
|
||||||
|
weatherX_ = 0;
|
||||||
|
weatherVelX_ *= -1.0f;
|
||||||
|
} else if (weatherX_ + weatherWidth_ > surfaceWidth) {
|
||||||
|
weatherX_ = (float)surfaceWidth - weatherWidth_;
|
||||||
|
weatherVelX_ *= -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 세로 경계
|
||||||
|
if (weatherY_ < 0) {
|
||||||
|
weatherY_ = 0;
|
||||||
|
weatherVelY_ *= -1.0f;
|
||||||
|
} else if (weatherY_ + weatherHeight_ > surfaceHeight) {
|
||||||
|
weatherY_ = (float)surfaceHeight - weatherHeight_;
|
||||||
|
weatherVelY_ *= -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 그리기 (기존의 블렌딩 로직 사용)
|
||||||
|
uint32_t* dstPixels = (uint32_t*)buffer.bits;
|
||||||
|
int dstStride = buffer.stride;
|
||||||
|
|
||||||
|
for (int y = 0; y < weatherHeight_; ++y) {
|
||||||
|
int targetY = static_cast<int>(weatherY_) + y;
|
||||||
|
if (targetY < 0 || targetY >= surfaceHeight) continue;
|
||||||
|
|
||||||
|
for (int x = 0; x < weatherWidth_; ++x) {
|
||||||
|
int targetX = static_cast<int>(weatherX_) + x;
|
||||||
|
if (targetX < 0 || targetX >= surfaceWidth) continue;
|
||||||
|
|
||||||
|
const uint8_t* src = &weatherPixels_[(y * weatherWidth_ + x) * 4];
|
||||||
|
uint8_t alpha = src[3];
|
||||||
|
if (alpha == 0) continue;
|
||||||
|
|
||||||
|
uint32_t* dst = &dstPixels[targetY * dstStride + targetX];
|
||||||
|
|
||||||
|
// 단순 합성을 위해 하드코딩된 ARGB/RGBA 순서 주의
|
||||||
|
uint8_t dr = (*dst >> 16) & 0xFF;
|
||||||
|
uint8_t dg = (*dst >> 8) & 0xFF;
|
||||||
|
uint8_t db = (*dst) & 0xFF;
|
||||||
|
|
||||||
|
uint8_t r = (src[0] * alpha + dr * (255 - alpha)) / 255;
|
||||||
|
uint8_t g = (src[1] * alpha + dg * (255 - alpha)) / 255;
|
||||||
|
uint8_t b = (src[2] * alpha + db * (255 - alpha)) / 255;
|
||||||
|
|
||||||
|
*dst = (0xFF << 24) | (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ANativeWindow_unlockAndPost(window);
|
ANativeWindow_unlockAndPost(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,9 @@ public:
|
|||||||
void setFadeDuration(int durationMs);
|
void setFadeDuration(int durationMs);
|
||||||
void setPageTurnDelay(int delayMs);
|
void setPageTurnDelay(int delayMs);
|
||||||
void setTransitionMode(int mode);
|
void setTransitionMode(int mode);
|
||||||
|
void setImageRenderFrame(int frame);
|
||||||
|
void updateWeatherBitmap(void* pixels, int width, int height);
|
||||||
|
void clearWeatherBitmap();
|
||||||
void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale);
|
void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale);
|
||||||
void renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
|
void renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
|
||||||
void renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
|
void renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
|
||||||
@ -84,4 +86,20 @@ private:
|
|||||||
std::mt19937 randomEngine_;
|
std::mt19937 randomEngine_;
|
||||||
|
|
||||||
void determineActiveAnimationMode();
|
void determineActiveAnimationMode();
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<uint8_t> weatherPixels_;
|
||||||
|
int weatherWidth_ = 0;
|
||||||
|
int weatherHeight_ = 0;
|
||||||
|
int imageFrame_ = 33;
|
||||||
|
// 위치 변수
|
||||||
|
float weatherX_ = 100.0f;
|
||||||
|
float weatherY_ = 100.0f;
|
||||||
|
|
||||||
|
// 속도 변수 (방향 및 속도 조절)
|
||||||
|
float weatherVelX_ = 2.5f;
|
||||||
|
float weatherVelY_ = 2.0f;
|
||||||
|
|
||||||
|
std::mutex weatherMutex_;
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -5,7 +5,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include "Renderer.h"
|
#include "Renderer.h"
|
||||||
#include "Preloader.h"
|
#include "Preloader.h"
|
||||||
|
#include <android/bitmap.h>
|
||||||
#define LOG_TAG "NativeRenderer"
|
#define LOG_TAG "NativeRenderer"
|
||||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
@ -113,6 +113,27 @@ Java_bums_lunatic_launcher_wall_NativeRenderer_nativeRender(JNIEnv* env, jobject
|
|||||||
renderer->renderFrame(window);
|
renderer->renderFrame(window);
|
||||||
ANativeWindow_release(window);
|
ANativeWindow_release(window);
|
||||||
}
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetWeatherBitmap(JNIEnv* env, jobject, jlong nativeHandle, jobject bitmap) {
|
||||||
|
Renderer* renderer = reinterpret_cast<Renderer*>(nativeHandle);
|
||||||
|
if (!renderer) return;
|
||||||
|
|
||||||
|
// 비트맵이 null로 넘어오면 데이터를 지움
|
||||||
|
if (bitmap == nullptr) {
|
||||||
|
renderer->clearWeatherBitmap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidBitmapInfo info;
|
||||||
|
void* pixels;
|
||||||
|
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) return;
|
||||||
|
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) return;
|
||||||
|
|
||||||
|
renderer->updateWeatherBitmap(pixels, info.width, info.height);
|
||||||
|
|
||||||
|
AndroidBitmap_unlockPixels(env, bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartNextPreload(JNIEnv* env, jobject, jlong nativeHandle, jint fd) {
|
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartNextPreload(JNIEnv* env, jobject, jlong nativeHandle, jint fd) {
|
||||||
@ -124,6 +145,18 @@ Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartNextPreload(JNIEnv* en
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetImageRenderFrame(JNIEnv* env, jobject, jlong nativeHandle, jint frame) {
|
||||||
|
Renderer* renderer = toNative<Renderer>(nativeHandle);
|
||||||
|
if (renderer) {
|
||||||
|
// Preloader가 Renderer의 일부가 되었다고 가정하고 호출 (추후 Renderer 수정 필요)
|
||||||
|
// 여기서는 간단하게 setNextMedia를 호출하는 것으로 변경합니다.
|
||||||
|
renderer->setImageRenderFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetAnimationSpeed(JNIEnv* env, jobject, jlong nativeHandle, jfloat speed) {
|
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetAnimationSpeed(JNIEnv* env, jobject, jlong nativeHandle, jfloat speed) {
|
||||||
Renderer* renderer = toNative<Renderer>(nativeHandle);
|
Renderer* renderer = toNative<Renderer>(nativeHandle);
|
||||||
|
|||||||
@ -1,19 +1,27 @@
|
|||||||
package bums.lunatic.launcher.wall
|
package bums.lunatic.launcher.wall
|
||||||
|
|
||||||
import android.app.WallpaperManager
|
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.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.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.service.wallpaper.WallpaperService
|
import android.service.wallpaper.WallpaperService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import androidx.work.ListenableWorker
|
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
|
import bums.lunatic.launcher.workers.LocationUpdateService
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class MyWallpaperService : WallpaperService() {
|
class MyWallpaperService : WallpaperService() {
|
||||||
|
|
||||||
@ -25,6 +33,18 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
Log.d(TAG, "onCreateEngine: New engine instance created.")
|
Log.d(TAG, "onCreateEngine: New engine instance created.")
|
||||||
return NativeRenderEngine()
|
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() {
|
inner class NativeRenderEngine : Engine() {
|
||||||
private lateinit var handlerThread: HandlerThread
|
private lateinit var handlerThread: HandlerThread
|
||||||
@ -136,6 +156,16 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
initializeRenderer()
|
initializeRenderer()
|
||||||
}
|
}
|
||||||
requestSyncRenderState()
|
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?.setAnimationMode(NativeRenderer.ANIMATION_MODE_PAN)
|
||||||
nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_FADE)
|
nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_FADE)
|
||||||
nativeRenderer?.setAnimationSpeed(1.0f)
|
nativeRenderer?.setAnimationSpeed(1.0f)
|
||||||
|
nativeRenderer?.setImageRenderFrame(70)
|
||||||
NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback)
|
NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback)
|
||||||
|
|
||||||
|
|
||||||
if (mediaFiles.isEmpty()) {
|
if (mediaFiles.isEmpty()) {
|
||||||
handler.post { loadMediaFiles() }
|
handler.post { loadMediaFiles() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler.removeCallbacks(updateWeatherRunnable)
|
||||||
|
// 즉시 첫 번째 비트맵 생성 및 전송
|
||||||
|
handler.post(updateWeatherRunnable)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val requiredSizeRatio = 0.5
|
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() {
|
private fun loadMediaFiles() {
|
||||||
loadFiles()
|
loadFiles()
|
||||||
if (mediaFiles.isNotEmpty()) {
|
if (mediaFiles.isNotEmpty()) {
|
||||||
@ -253,6 +393,7 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
} ?: run {
|
} ?: run {
|
||||||
Log.e(TAG, "Failed to get fd for initial media: ${initialFile.absolutePath}")
|
Log.e(TAG, "Failed to get fd for initial media: ${initialFile.absolutePath}")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mediaDir = File(File(this@MyWallpaperService.getExternalFilesDir(null), "completed_torrents"), "Images")
|
val mediaDir = File(File(this@MyWallpaperService.getExternalFilesDir(null), "completed_torrents"), "Images")
|
||||||
@ -269,6 +410,20 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
} ?: run {
|
} ?: run {
|
||||||
Log.e(TAG, "Callback: Failed to get fd for ${nextFile.absolutePath}")
|
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
|
package bums.lunatic.launcher.wall
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
|
|
||||||
@ -29,7 +30,11 @@ open class NativeRenderer {
|
|||||||
nativeHandle = 0L
|
nativeHandle = 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun setWeatherBitmap(bitmap: Bitmap?) {
|
||||||
|
if (nativeHandle != 0L) {
|
||||||
|
nativeSetWeatherBitmap(nativeHandle, bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
fun render(surface: Surface) {
|
fun render(surface: Surface) {
|
||||||
if (nativeHandle != 0L) {
|
if (nativeHandle != 0L) {
|
||||||
nativeRender(nativeHandle, surface)
|
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 JNI declarations ---
|
||||||
private external fun nativeStartRenderLoop(nativeHandle: Long, surface: Surface)
|
private external fun nativeStartRenderLoop(nativeHandle: Long, surface: Surface)
|
||||||
@ -104,7 +115,9 @@ open class NativeRenderer {
|
|||||||
private external fun nativeInit(): Long
|
private external fun nativeInit(): Long
|
||||||
private external fun nativeDestroy(nativeHandle: Long)
|
private external fun nativeDestroy(nativeHandle: Long)
|
||||||
private external fun nativeRender(nativeHandle: Long, surface: Surface)
|
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 nativeStartNextPreload(nativeHandle: Long, fd: Int)
|
||||||
|
private external fun nativeSetImageRenderFrame(nativeHandle: Long, frame: Int)
|
||||||
private external fun nativeSetAnimationSpeed(nativeHandle: Long, speed: Float)
|
private external fun nativeSetAnimationSpeed(nativeHandle: Long, speed: Float)
|
||||||
private external fun nativeSetAnimationMode(nativeHandle: Long, mode: Int)
|
private external fun nativeSetAnimationMode(nativeHandle: Long, mode: Int)
|
||||||
private external fun nativeSetFadeDuration(nativeHandle: Long, duration: Int)
|
private external fun nativeSetFadeDuration(nativeHandle: Long, duration: Int)
|
||||||
|
|||||||
@ -41,6 +41,7 @@ class LocationUpdateService : Service(), LocationListener {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var longitude: Double = 0.0
|
var longitude: Double = 0.0
|
||||||
|
var lastWeather = mutableListOf<String>()
|
||||||
var latitude: Double = 0.0
|
var latitude: Double = 0.0
|
||||||
fun pushLocation(context: Context, lat :Double, long : Double) {
|
fun pushLocation(context: Context, lat :Double, long : Double) {
|
||||||
try {
|
try {
|
||||||
@ -91,7 +92,7 @@ class LocationUpdateService : Service(), LocationListener {
|
|||||||
// 응답 받아서 처리
|
// 응답 받아서 처리
|
||||||
val body: ResponseBody? = response.body
|
val body: ResponseBody? = response.body
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
// Blog.LOGE("Location >>> ${body.string()}")
|
Blog.LOGE("Location >>> ${body.string()}")
|
||||||
}
|
}
|
||||||
} else Blog.LOGE("telegram Error Occurred")
|
} else Blog.LOGE("telegram Error Occurred")
|
||||||
}
|
}
|
||||||
@ -99,6 +100,41 @@ class LocationUpdateService : Service(), LocationListener {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}, 5, TimeUnit.SECONDS)
|
}, 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}")
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user