diff --git a/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt index 6e5e8cd6ef..1889b54fb4 100644 --- a/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt +++ b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt @@ -26,6 +26,7 @@ import app.lawnchair.wallpaper.service.Wallpaper import com.android.launcher3.R import com.android.launcher3.util.Themes import com.android.launcher3.views.ActivityContext +import java.io.File import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -129,13 +130,25 @@ class WallpaperCarouselView @JvmOverloads constructor( addView(cardView) CoroutineScope(Dispatchers.IO).launch { - val bitmap = BitmapFactory.decodeFile(wallpaper.imagePath) - withContext(Dispatchers.Main) { - (cardView.getChildAt(0) as? ImageView)?.apply { - setImageBitmap(bitmap) + val wallpaperFile = File(wallpaper.imagePath) + if (wallpaperFile.exists()) { + val bitmap = BitmapFactory.decodeFile(wallpaper.imagePath) + withContext(Dispatchers.Main) { + (cardView.getChildAt(0) as? ImageView)?.apply { + setImageBitmap(bitmap) + } + if (index == currentItemIndex) { + addIconFrameToCenter(cardView) + } } - if (index == currentItemIndex) { - addIconFrameToCenter(cardView) + } else { + Log.e("WallpaperCarouselView", "File not found: ${wallpaper.imagePath}") + withContext(Dispatchers.Main) { + (cardView.getChildAt(0) as? ImageView)?.apply { + setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder), + ) + } } } } @@ -163,6 +176,8 @@ class WallpaperCarouselView @JvmOverloads constructor( wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM) + viewModel.updateWallpaperRank(wallpaper) + withContext(Dispatchers.Main) { currentCardView.removeView(loadingSpinner) addIconFrameToCenter(currentCardView) diff --git a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt index 2e3a03bc4b..64048a1390 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt @@ -5,17 +5,13 @@ import android.content.Context import app.lawnchair.util.MainThreadInitializedObject import app.lawnchair.util.requireSystemService import app.lawnchair.wallpaper.WallpaperColorsCompat.Companion.HINT_SUPPORTS_DARK_THEME -import app.lawnchair.wallpaper.service.WallpaperService import com.android.launcher3.Utilities -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch sealed class WallpaperManagerCompat(val context: Context) { private val listeners = mutableListOf() private val colorHints: Int get() = wallpaperColors?.colorHints ?: 0 - protected val wallpaperManager: WallpaperManager = context.requireSystemService() + val wallpaperManager: WallpaperManager = context.requireSystemService() abstract val wallpaperColors: WallpaperColorsCompat? @@ -33,10 +29,6 @@ sealed class WallpaperManagerCompat(val context: Context) { listeners.toTypedArray().forEach { it.onColorsChanged() } - - CoroutineScope(Dispatchers.IO).launch { - WallpaperService(context).saveWallpaper(wallpaperManager) - } } interface OnColorsChangedListener { diff --git a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt index 0e4d13bd91..55877ddcff 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt @@ -1,28 +1,65 @@ package app.lawnchair.wallpaper.model +import android.app.WallpaperManager import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import app.lawnchair.wallpaper.WallpaperManagerCompat import app.lawnchair.wallpaper.service.Wallpaper import app.lawnchair.wallpaper.service.WallpaperDatabase +import app.lawnchair.wallpaper.service.WallpaperService import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock class WallpaperViewModel(context: Context) : ViewModel() { private val dao = WallpaperDatabase.INSTANCE.get(context).wallpaperDao() + private val service = WallpaperService.INSTANCE.get(context) + + private val wallpaperManagerCompat = WallpaperManagerCompat.INSTANCE.get(context) private val _wallpapers = MutableLiveData>() val wallpapers: LiveData> = _wallpapers + private val mutex = Mutex() + + private val listener = object : WallpaperManagerCompat.OnColorsChangedListener { + override fun onColorsChanged() { + viewModelScope.launch { + mutex.withLock { + saveWallpaper(wallpaperManagerCompat.wallpaperManager) + } + } + } + } + init { loadTopWallpapers() + wallpaperManagerCompat.addOnChangeListener(listener) + } + + private suspend fun saveWallpaper(wallpaperManager: WallpaperManager) { + service.saveWallpaper(wallpaperManager) + refreshWallpapers() + } + + private suspend fun refreshWallpapers() { + val topWallpapers = dao.getTopWallpapers() + _wallpapers.postValue(topWallpapers) } private fun loadTopWallpapers() { viewModelScope.launch { - val topWallpapers = dao.getTopWallpapers() - _wallpapers.postValue(topWallpapers) + mutex.withLock { + refreshWallpapers() + } } } + + suspend fun updateWallpaperRank(wallpaper: Wallpaper) { + service.updateWallpaperRank(wallpaper) + loadTopWallpapers() + } } diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt b/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt index 7116943004..df8aa46b2b 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt @@ -9,4 +9,5 @@ data class Wallpaper( val imagePath: String, val rank: Int, val timestamp: Long, + val checksum: String? = null, ) diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt index 0c901e68d1..56c3a66df1 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt @@ -17,6 +17,9 @@ interface WallpaperDao { @Query("UPDATE wallpapers SET rank = rank + 1 WHERE rank >= :rank") suspend fun updateRank(rank: Int) + @Query("UPDATE wallpapers SET rank = :rank, timestamp = :timestamp WHERE id = :id") + suspend fun updateWallpaper(id: Long, rank: Int, timestamp: Long) + @Query("DELETE FROM wallpapers WHERE id = :id") suspend fun deleteWallpaper(id: Long) diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt index 05953ac225..af7bf1cb8b 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt @@ -3,11 +3,13 @@ package app.lawnchair.wallpaper.service import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteDatabase import app.lawnchair.util.MainThreadInitializedObject import kotlinx.coroutines.runBlocking -@Database(entities = [Wallpaper::class], version = 1, exportSchema = false) +@Database(entities = [Wallpaper::class], version = 2, exportSchema = false) abstract class WallpaperDatabase : RoomDatabase() { abstract fun wallpaperDao(): WallpaperDao @@ -23,12 +25,17 @@ abstract class WallpaperDatabase : RoomDatabase() { } companion object { + private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE wallpapers ADD COLUMN checksum TEXT DEFAULT 'undefined'") + } + } val INSTANCE = MainThreadInitializedObject { context -> Room.databaseBuilder( context, WallpaperDatabase::class.java, "wallpaper_database", - ).build() + ).addMigrations(MIGRATION_1_2).build() } } } diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt index 076102692e..d27fde28de 100644 --- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt +++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt @@ -10,6 +10,7 @@ import com.android.launcher3.util.MainThreadInitializedObject import com.android.launcher3.util.SafeCloseable import java.io.File import java.io.FileOutputStream +import java.security.MessageDigest import kotlinx.coroutines.runBlocking class WallpaperService(val context: Context) : SafeCloseable { @@ -29,35 +30,60 @@ class WallpaperService(val context: Context) : SafeCloseable { } } + private fun calculateChecksum(imageData: ByteArray): String { + return MessageDigest.getInstance("MD5") + .digest(imageData) + .joinToString("") { "%02x".format(it) } + } + private suspend fun saveWallpaper(imageData: ByteArray) { val timestamp = System.currentTimeMillis() - - val topWallpapers = dao.getTopWallpapers() - val imagePath = saveImageToAppStorage(imageData) - if (topWallpapers.size < 4) { - val wallpaper = Wallpaper(imagePath = imagePath, rank = topWallpapers.size, timestamp = timestamp) + val checksum = calculateChecksum(imageData) + + val existingWallpapers = dao.getTopWallpapers() + + if (existingWallpapers.any { it.checksum == checksum }) { + Log.d("WallpaperService", "Wallpaper already exists with checksum: $checksum") + return + } + + if (existingWallpapers.size < 4) { + val wallpaper = Wallpaper(imagePath = imagePath, rank = existingWallpapers.size, timestamp = timestamp, checksum = checksum) dao.insert(wallpaper) } else { - val lowestRankedWallpaper = topWallpapers.minByOrNull { it.timestamp } + val lowestRankedWallpaper = existingWallpapers.minByOrNull { it.timestamp } if (lowestRankedWallpaper != null) { dao.deleteWallpaper(lowestRankedWallpaper.id) deleteWallpaperFile(lowestRankedWallpaper.imagePath) } - for (wallpaper in topWallpapers) { + for (wallpaper in existingWallpapers) { if (wallpaper.rank >= (lowestRankedWallpaper?.rank ?: 0)) { dao.updateRank(wallpaper.rank) } } - val wallpaper = Wallpaper(imagePath = imagePath, rank = 0, timestamp = timestamp) + val wallpaper = Wallpaper(imagePath = imagePath, rank = 0, timestamp = timestamp, checksum = checksum) dao.insert(wallpaper) } } + suspend fun updateWallpaperRank(selectedWallpaper: Wallpaper) { + val topWallpapers = dao.getTopWallpapers() + val currentTime = System.currentTimeMillis() + + dao.updateWallpaper(selectedWallpaper.id, rank = 0, timestamp = currentTime) + + for (wallpaper in topWallpapers) { + if (wallpaper.id != selectedWallpaper.id) { + dao.updateRank(wallpaper.rank) + } + } + } + fun getTopWallpapers(): List = runBlocking { val wallpapers = dao.getTopWallpapers() wallpapers.ifEmpty { emptyList() }