Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bitmap wallpaper setting on main thread #132

Merged
merged 6 commits into from
May 15, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/java/com/b_lam/resplash/di/ManagerModule.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.b_lam.resplash.di

import android.app.WallpaperManager
import com.b_lam.resplash.util.NotificationManager
import com.b_lam.resplash.util.download.DownloadManagerWrapper
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

val managerModule = module {

single(createdAtStart = true) { NotificationManager(androidContext()) }
single { DownloadManagerWrapper(androidContext()) }
single { WallpaperManager.getInstance(androidApplication()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
Expand All @@ -36,8 +34,12 @@ import com.b_lam.resplash.util.download.*
import com.b_lam.resplash.worker.DownloadWorker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.*
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.IOException
import java.lang.Exception
import java.util.concurrent.Executors

class PhotoDetailActivity :
BaseActivity(R.layout.activity_photo_detail), TagAdapter.ItemEventCallback {
Expand All @@ -52,6 +54,8 @@ class PhotoDetailActivity :

private var snackbar: Snackbar? = null

private val wallpaperManager: WallpaperManager by inject()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -73,10 +77,12 @@ class PhotoDetailActivity :
photo != null -> id = photo.id
photoId != null -> id = photoId
else -> null
} ?.let {
}?.let {
if (photo != null) setup(photo)
viewModel.photoDetailsLiveData(id).observe(this) { photoDetails ->
if (photo == null) { setup(photoDetails) }
if (photo == null) {
setup(photoDetails)
}
displayPhotoDetails(photoDetails)
}
} ?: run {
Expand Down Expand Up @@ -131,7 +137,12 @@ class PhotoDetailActivity :

private fun setup(photo: Photo) {
val url = getPhotoUrl(photo, sharedPreferencesRepository.loadQuality)
binding.photoImageView.loadPhotoUrlWithThumbnail(url, photo.urls.thumb, photo.color, centerCrop = true)
binding.photoImageView.loadPhotoUrlWithThumbnail(
url,
photo.urls.thumb,
photo.color,
centerCrop = true
)
binding.photoImageView.setOnClickListener {
Intent(this, PhotoZoomActivity::class.java).apply {
putExtra(PhotoZoomActivity.EXTRA_PHOTO_URL, url)
Expand Down Expand Up @@ -173,7 +184,13 @@ class PhotoDetailActivity :
likesCountTextView.text = (photo.likes ?: 0).toPrettyString()
tagRecyclerView.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false).apply {
addItemDecoration(SpacingItemDecoration(context, R.dimen.keyline_6, RecyclerView.HORIZONTAL))
addItemDecoration(
SpacingItemDecoration(
context,
R.dimen.keyline_6,
RecyclerView.HORIZONTAL
)
)
}
adapter = TagAdapter(this@PhotoDetailActivity).apply { submitList(photo.tags) }
}
Expand Down Expand Up @@ -257,8 +274,13 @@ class PhotoDetailActivity :
val downloadManagerWrapper: DownloadManagerWrapper by inject()
viewModel.downloadId = downloadManagerWrapper.downloadPhoto(url, photo.fileName)
} else {
viewModel.downloadUUID = DownloadWorker.enqueueDownload(applicationContext,
DownloadAction.DOWNLOAD, url, photo.fileName, photo.id)
viewModel.downloadUUID = DownloadWorker.enqueueDownload(
applicationContext,
DownloadAction.DOWNLOAD,
url,
photo.fileName,
photo.id
)
}
} else {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode = 0)
Expand All @@ -272,12 +294,21 @@ class PhotoDetailActivity :
val downloadManagerWrapper: DownloadManagerWrapper by inject()
viewModel.downloadId = downloadManagerWrapper.downloadWallpaper(url, photo.fileName)
} else {
viewModel.downloadUUID = DownloadWorker.enqueueDownload(applicationContext,
DownloadAction.WALLPAPER, url, photo.fileName, photo.id)
viewModel.downloadUUID = DownloadWorker.enqueueDownload(
applicationContext,
DownloadAction.WALLPAPER,
url,
photo.fileName,
photo.id
)
}

snackbar = Snackbar
.make(binding.coordinatorLayout, R.string.setting_wallpaper, Snackbar.LENGTH_INDEFINITE)
.make(
binding.coordinatorLayout,
R.string.setting_wallpaper,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.cancel) { cancelDownload() }
.setActionTextColor(ContextCompat.getColor(this, R.color.red_400))
.setAnchorView(R.id.set_as_wallpaper_button)
Expand All @@ -298,7 +329,10 @@ class PhotoDetailActivity :
applyWallpaper(it)
}
STATUS_FAILED ->
binding.coordinatorLayout.showSnackBar(R.string.oops, anchor = R.id.set_as_wallpaper_button)
binding.coordinatorLayout.showSnackBar(
R.string.oops,
anchor = R.id.set_as_wallpaper_button
)
}
} else if (action == DownloadAction.DOWNLOAD) {
when (status) {
Expand All @@ -310,22 +344,39 @@ class PhotoDetailActivity :

private fun applyWallpaper(uri: Uri) {
try {
startActivity(WallpaperManager.getInstance(this).getCropAndSetWallpaperIntent(uri))
startActivity(wallpaperManager.getCropAndSetWallpaperIntent(uri))
} catch (e: IllegalArgumentException) {
var bitmap: Bitmap? = null
try {
bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(contentResolver, uri))
} else {
MediaStore.Images.Media.getBitmap(contentResolver, uri)
}
WallpaperManager.getInstance(applicationContext).setBitmap(bitmap)
toast("Wallpaper set successfully")
} catch (e: Exception) {
error("Error setting wallpaper", e)
viewModel.prepareBitmapFromUri(contentResolver, uri)
observerWallpaperBitmapResult()
}
}

private fun observerWallpaperBitmapResult() {
viewModel.wallpaperBitmap.observe(this) { result ->
if (result is Result.Success) {
setWallpaperWithBitmap(result.value)
} else {
toast("Failed to set wallpaper")
} finally {
bitmap?.recycle()
}
}
}

private fun setWallpaperWithBitmap(bitmap: Bitmap) {
lifecycleScope.launch {
var isWallpaperSet = false
withContext((Executors.newSingleThreadExecutor().asCoroutineDispatcher())) {
try {
wallpaperManager.setBitmap(bitmap)
isWallpaperSet = true
} catch (exception: IOException) {
error("Error setting wallpaper bitmap: $exception")
}
}
withContext(Dispatchers.Main) {
val resultMessage = if (isWallpaperSet) {
"Wallpaper set successfully"
} else "Error setting wallpaper"
[email protected](resultMessage)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.b_lam.resplash.ui.photo.detail

import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.lifecycle.*
import com.b_lam.resplash.data.collection.model.Collection
import com.b_lam.resplash.data.photo.model.Photo
Expand All @@ -8,9 +14,11 @@ import com.b_lam.resplash.domain.collection.CollectionRepository
import com.b_lam.resplash.domain.login.LoginRepository
import com.b_lam.resplash.domain.photo.PhotoRepository
import com.b_lam.resplash.util.Result
import com.b_lam.resplash.util.error
import com.b_lam.resplash.util.livedata.lazyMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*

class PhotoDetailViewModel(
Expand Down Expand Up @@ -40,6 +48,9 @@ class PhotoDetailViewModel(
private val _userCollections = MutableLiveData<MutableList<Collection>?>()
val userCollections: LiveData<MutableList<Collection>?> = _userCollections

private val _wallpaperBitmap = MutableLiveData<Result<Bitmap>>()
val wallpaperBitmap: LiveData<Result<Bitmap>> = _wallpaperBitmap

var downloadId: Long? = null
var downloadUUID: UUID? = null

Expand Down Expand Up @@ -143,5 +154,35 @@ class PhotoDetailViewModel(

emit(createResult)
}

fun prepareBitmapFromUri(
contentResolver: ContentResolver,
uri: Uri
) {
viewModelScope.launch {
val bitmapResult = decodeBitmap(contentResolver, uri)
_wallpaperBitmap.postValue(bitmapResult)
}
}

private suspend fun decodeBitmap(
contentResolver: ContentResolver,
uri: Uri
): Result<Bitmap> {
return withContext(Dispatchers.IO) {
try {
val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(contentResolver, uri))
} else {
MediaStore.Images.Media.getBitmap(contentResolver, uri)
}
Result.Success(bitmap)
} catch (e: Exception) {
val message = "Error decoding bitmap"
error(message, e)
Result.Error(error = message)
}
}
}
}