Skip to content

Commit

Permalink
Always share from image cache
Browse files Browse the repository at this point in the history
Retrieve image from `ContentProvider` rather than network. Rebuild
missing cache by calling `ContentResolver.openFileDescriptor()` if
needed.

Also polish share intent and command names.
  • Loading branch information
strievi committed Jun 15, 2020
1 parent 2173356 commit 02cbb9f
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 101 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.google.android.apps.muzei:muzei-api:3.4.0-alpha02'
implementation "androidx.core:core-ktx:$rootProject.ext.ktxVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.ext.coroutinesVersion"
implementation "android.arch.work:work-runtime-ktx:$rootProject.ext.workManagerVersion"
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'de.greenrobot:eventbus:2.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.android.support:multidex:1.0.3'
}
repositories {
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/java/de/devmil/common/utils/BroadcastReceiverExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* From: https://github.com/romannurik/muzei/blob/d9899b5d35336d7fdfd6007b13506b249afa754e/extensions/src/main/java/com/google/android/apps/muzei/util/BroadcastReceiverExt.kt
*/

package de.devmil.common.utils

import android.content.BroadcastReceiver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

fun BroadcastReceiver.goAsync(
coroutineScope: CoroutineScope = GlobalScope,
block: suspend () -> Unit
) {
val result = goAsync()
coroutineScope.launch {
try {
block()
} finally {
// Always call finish(), even if the coroutineScope was cancelled
result.finish()
}
}
}
40 changes: 40 additions & 0 deletions app/src/main/java/de/devmil/common/utils/ContextExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* From: https://github.com/romannurik/muzei/blob/f929ba34a9376f5267bdaa0f8497f39671fcb1de/extensions/src/main/java/com/google/android/apps/muzei/util/ContextExt.kt
*/

package de.devmil.common.utils

import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
* Creates and shows a [Toast] with the given [text]
*
* @param duration Toast duration, defaults to [Toast.LENGTH_SHORT]
*/
fun Context.toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, text, duration).apply { show() }
}

/**
* Creates and shows a [Toast] with text from a resource
*
* @param resId Resource id of the string resource to use
* @param duration Toast duration, defaults to [Toast.LENGTH_SHORT]
*/
fun Context.toast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, resId, duration).apply { show() }
}

fun Context.toastFromBackground(
@StringRes resId: Int,
duration: Int = Toast.LENGTH_SHORT
) {
GlobalScope.launch(Dispatchers.Main) {
Toast.makeText(this@toastFromBackground, resId, duration).apply { show() }
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
package de.devmil.muzei.bingimageoftheday

import android.annotation.TargetApi
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import android.content.*
import android.os.*
import androidx.core.content.FileProvider
import com.google.android.apps.muzei.api.provider.Artwork
import com.google.android.apps.muzei.api.provider.ProviderContract
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
import de.devmil.common.utils.LogUtil
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import de.devmil.common.utils.goAsync
import de.devmil.common.utils.toastFromBackground

class BingImageOfTheDayBroadcastReceiver : BroadcastReceiver() {
companion object {
Expand All @@ -46,100 +31,83 @@ class BingImageOfTheDayBroadcastReceiver : BroadcastReceiver() {
}

override fun onReceive(context: Context, intent: Intent) {
val providerClient = ProviderContract.getProviderClient(
context, BuildConfig.BING_IMAGE_OF_THE_DAY_AUTHORITY)
providerClient.lastAddedArtwork?.let {
goAsync {
when (intent.action) {
INTENT_ACTION_SHARE -> {
shareCurrentImage(context, it)
shareLast(context)
}
INTENT_ACTION_OPEN -> {
openCurrentImage(context, it)
openLast(context)
}
}
}
}

@TargetApi(26)
fun getLocalBitmapUri(bmp: Bitmap?, context: Context?): Uri? {
var bmpUri: Uri? = null
bmp?.let { bitmap ->
try {
context?.let { ctx ->
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-kkmmss"))
val imagePath = File(context.cacheDir, "images")
imagePath.mkdirs()
val outputFile = File(imagePath, "output-$timestamp.png")
val out = FileOutputStream(outputFile)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out)
out.close()
bmpUri = FileProvider.getUriForFile(context, "de.devmil.muzei.bingimageoftheday.ImageFileProvider", outputFile)
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(context, "Error downloading the image to share", Toast.LENGTH_LONG).show()
}
}
return bmpUri
}

private fun shareCurrentImage(context: Context, artwork: Artwork?) {
LogUtil.LOGD(TAG, "got share request")
artwork?.let {
var shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
val shareMessage = context?.getString(R.string.command_share_message, it.byline)
shareIntent.putExtra(Intent.EXTRA_TEXT, "$shareMessage - ${it.persistentUri.toString()}")
shareIntent.type = "text/plain"
//shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

LogUtil.LOGD(TAG, "Sharing ${it.persistentUri}")

//For API > 26: download image and attach that
if(Build.VERSION.SDK_INT >= 26) {
val uiHandler = Handler(Looper.getMainLooper())
uiHandler.post {
Picasso.with(context).load(it.persistentUri).into(object : Target {
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
LogUtil.LOGD(TAG, "Downloading ${it.persistentUri}")
private fun shareLast(context: Context) {
LogUtil.LOGD(TAG, "Got share request")
val success = ProviderContract.getProviderClient(
context, BuildConfig.BING_IMAGE_OF_THE_DAY_AUTHORITY).run {
lastAddedArtwork?.let { artwork ->
// TODO: store cacheFilename into artwork metadata and retrieve it from here
val cacheFilename = artwork.persistentUri.toString().substringAfterLast("id=OHR.")
try {
cacheFilename.let { filename ->
context.cacheDir?.resolve("images")?.apply { mkdir() }?.apply { deleteOnExit() }?.resolve(filename)
}?.let { cacheFile ->
if (!cacheFile.exists()) {
if (artwork.data.exists()) {
artwork.data.copyTo(cacheFile)
} else {
// Attempt to rebuild artwork cache
val artworkUri = ContentUris.withAppendedId(contentUri, artwork.id)
context.contentResolver.openFileDescriptor(
artworkUri, "r").run {
ParcelFileDescriptor.AutoCloseInputStream(this).copyTo(cacheFile.outputStream())
}
}
}

override fun onBitmapFailed(errorDrawable: Drawable?) {
Toast.makeText(context, "Error downloading the image to share", Toast.LENGTH_LONG).show()
val shareMessageBuilder = StringBuilder()
shareMessageBuilder.apply {
if (artwork.title.isNullOrEmpty()) {
append(artwork.byline)
} else {
append("${artwork.title} - ${artwork.byline}")
}
append(" #BingImageOfTheDay")
if (artwork.webUri != null) {
append("\n\n${artwork.webUri}")
}
}

override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
shareIntent.putExtra(Intent.EXTRA_STREAM, getLocalBitmapUri(bitmap, context))
shareIntent.type = "image/png"

executeIntentSharing(context, shareIntent)
Intent(Intent.ACTION_SEND).apply {
val shareUri = FileProvider.getUriForFile(context, "de.devmil.muzei.bingimageoftheday.ImageFileProvider", cacheFile)
putExtra(Intent.EXTRA_TEXT, shareMessageBuilder.toString())
putExtra(Intent.EXTRA_STREAM, shareUri)
clipData = ClipData(shareMessageBuilder.toString(), arrayOf("image/*"), ClipData.Item(shareUri))
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
type = "image/*"
}.let {
context.startActivity(Intent.createChooser(it, context.getString(R.string.command_share_title)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
})
true
} ?: false
} catch (e: Exception) {
LogUtil.LOGE(TAG, "An exception occurred while sharing file", e)
false
}
} else { // SDK < 26 => directly share (the URL)
executeIntentSharing(context, shareIntent)
}
} ?: false
}
if (!success) {
context.toastFromBackground(R.string.command_share_error)
}
}

private fun executeIntentSharing(context: Context, intent: Intent) {
var shareIntent = Intent.createChooser(intent, context?.getString(R.string.command_share_title) ?: "")
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

context?.startActivity(shareIntent)
}

private fun openCurrentImage(context: Context, artwork: Artwork?) {
LogUtil.LOGD(TAG, "got open request")
artwork?.let {
var openIntent = Intent(Intent.ACTION_VIEW)

LogUtil.LOGD(TAG, "Opening ${it.persistentUri}")

openIntent.data = it.persistentUri
openIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

context?.startActivity(openIntent)
private fun openLast(context: Context) {
LogUtil.LOGD(TAG, "Got open request")
ProviderContract.getProviderClient(
context, BuildConfig.BING_IMAGE_OF_THE_DAY_AUTHORITY).run {
lastAddedArtwork?.let { artwork ->
context.startActivity(Intent(Intent.ACTION_VIEW, artwork.persistentUri).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} ?: context.toastFromBackground(R.string.command_open_error)
}
}
}
7 changes: 4 additions & 3 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<string name="copyright">© 2017 Devmil Solutions - Michael Lamers</string>
<string name="activity_license_info_license_info_headline">License Info</string>
<string name="license_info_activity_label">About</string>
<string name="command_share_message">My current Android wallpaper: \'%1$s\'</string>
<string name="command_share_title">Share current Bing image</string>
<string name="command_open_title">Open current Bing image URL</string>
<string name="command_share_title">Share artwork</string>
<string name="command_open_title">Open artwork URL</string>
<string name="command_share_error">Cannot share artwork.</string>
<string name="command_open_error">Cannot open artwork URL.</string>
</resources>
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
buildscript {
ext {
kotlin_version = '1.3.21'
coroutinesVersion = '1.2.1'
workManagerVersion = "1.0.0-alpha10"
ktxVersion = "1.0.1"
}
Expand Down

0 comments on commit 02cbb9f

Please sign in to comment.