Skip to content

Commit

Permalink
fix: bitmap wallpaper setting on main thread (#132)
Browse files Browse the repository at this point in the history
* fix: bitmap wallpaper setting on main thread

* update: formatting

Co-authored-by: Kartik Sharma <[email protected]>

* update: formatting

Co-authored-by: Kartik Sharma <[email protected]>

* update: handle wallpaper bitmap setting exception

* update: method name, if-else formatting

* fix: observing to bitmap result forever

Co-authored-by: Vipul Asri <[email protected]>
Co-authored-by: Kartik Sharma <[email protected]>
  • Loading branch information
3 people authored May 15, 2021
1 parent c7c6b26 commit b758a17
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 27 deletions.
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 @@ -33,11 +31,15 @@ import com.b_lam.resplash.ui.user.UserActivity
import com.b_lam.resplash.ui.widget.recyclerview.SpacingItemDecoration
import com.b_lam.resplash.util.*
import com.b_lam.resplash.util.download.*
import com.b_lam.resplash.util.livedata.observeOnce
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.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,41 @@ 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)
observeWallpaperBitmapResult()
}
}

private fun observeWallpaperBitmapResult() {
viewModel.wallpaperBitmap.observeOnce(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"
}
this@PhotoDetailActivity.toast(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)
}
}
}
}

0 comments on commit b758a17

Please sign in to comment.