diff --git a/CHANGELOG.md b/CHANGELOG.md
index 906d311b5..840e6e0a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 3.5.3
+
+#### What's New
+- Basic Tasker integration for safely starting Auxio's service
+
+#### What's Improved
+- Added support for informal singular-spaced tags like `album artist` in
+file metadata
+
+#### What's Fixed
+- Fix "Foreground not allowed" music loading crash from starting too early
+- Fixed widget not loading on some devices due to the cover being too large
+
## 3.5.2
#### What's Fixed
diff --git a/README.md b/README.md
index 6a3e6a632..191ca51ec 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
Auxio
A simple, rational music player for android.
-
-
+
+
diff --git a/app/build.gradle b/app/build.gradle
index fd4bfacc9..98ded55d6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,13 +16,13 @@ android {
// it here so that binary stripping will work.
// TODO: Eventually you might just want to start vendoring the FFMpeg extension so the
// NDK use is unified
- ndkVersion = "25.2.9519653"
+ ndkVersion "26.3.11579264"
namespace "org.oxycblt.auxio"
defaultConfig {
applicationId namespace
- versionName "3.5.2"
- versionCode 48
+ versionName "3.5.3"
+ versionCode 49
minSdk 24
targetSdk 34
@@ -151,6 +151,9 @@ dependencies {
// Speed dial
implementation "com.leinardi.android:speed-dial:3.3.0"
+ // Tasker integration
+ implementation 'com.joaomgcd:taskerpluginlibrary:0.4.10'
+
// Testing
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
testImplementation "junit:junit:4.13.2"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 960d2c8ae..cbf7ea304 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -135,5 +135,15 @@
android:resource="@xml/widget_info" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioService.kt b/app/src/main/java/org/oxycblt/auxio/AuxioService.kt
index 64121e6d1..f0352d523 100644
--- a/app/src/main/java/org/oxycblt/auxio/AuxioService.kt
+++ b/app/src/main/java/org/oxycblt/auxio/AuxioService.kt
@@ -43,24 +43,21 @@ class AuxioService : MediaLibraryService(), ForegroundListener {
}
override fun onBind(intent: Intent?): IBinder? {
- handleIntent(intent)
+ onHandleForeground(intent)
return super.onBind(intent)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// TODO: Start command occurring from a foreign service basically implies a detached
// service, we might need more handling here.
- handleIntent(intent)
+ onHandleForeground(intent)
return super.onStartCommand(intent, flags, startId)
}
- private fun handleIntent(intent: Intent?) {
- val nativeStart = intent?.getBooleanExtra(INTENT_KEY_NATIVE_START, false) ?: false
- if (!nativeStart) {
- // Some foreign code started us, no guarantees about foreground stability. Figure
- // out what to do.
- mediaSessionFragment.handleNonNativeStart()
- }
+ private fun onHandleForeground(intent: Intent?) {
+ val startId = intent?.getIntExtra(INTENT_KEY_START_ID, -1) ?: -1
+ indexingFragment.start()
+ mediaSessionFragment.start(startId)
}
override fun onTaskRemoved(rootIntent: Intent?) {
@@ -86,6 +83,7 @@ class AuxioService : MediaLibraryService(), ForegroundListener {
if (change == ForegroundListener.Change.MEDIA_SESSION) {
mediaSessionFragment.createNotification {
startForeground(it.notificationId, it.notification)
+ isForeground = true
}
}
// Nothing changed, but don't show anything music related since we can always
@@ -94,16 +92,21 @@ class AuxioService : MediaLibraryService(), ForegroundListener {
indexingFragment.createNotification {
if (it != null) {
startForeground(it.code, it.build())
+ isForeground = true
} else {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
+ isForeground = false
}
}
}
}
companion object {
+ var isForeground = false
+ private set
+
// This is only meant for Auxio to internally ensure that it's state management will work.
- const val INTENT_KEY_NATIVE_START = BuildConfig.APPLICATION_ID + ".service.NATIVE_START"
+ const val INTENT_KEY_START_ID = BuildConfig.APPLICATION_ID + ".service.START_ID"
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
index 86f3d1984..eb1a27d53 100644
--- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
+++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
@@ -59,6 +59,10 @@ object IntegerTable {
const val INDEXER_NOTIFICATION_CODE = 0xA0A1
/** MainActivity Intent request code */
const val REQUEST_CODE = 0xA0C0
+ /** Activity AuxioService Start ID */
+ const val START_ID_ACTIVITY = 0xA050
+ /** Tasker AuxioService Start ID */
+ const val START_ID_TASKER = 0xA051
/** RepeatMode.NONE */
const val REPEAT_MODE_NONE = 0xA100
/** RepeatMode.ALL */
@@ -133,7 +137,4 @@ object IntegerTable {
const val PLAY_SONG_FROM_PLAYLIST = 0xA123
/** PlaySong.ByItself */
const val PLAY_SONG_BY_ITSELF = 0xA124
- const val PLAYER_COMMAND_INC_REPEAT_MODE = 0xA125
- const val PLAYER_COMMAND_TOGGLE_SHUFFLE = 0xA126
- const val PLAYER_COMMAND_EXIT = 0xA127
}
diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
index 42ad2a134..ab5474c9c 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
@@ -71,11 +71,11 @@ class MainActivity : AppCompatActivity() {
startService(
Intent(this, AuxioService::class.java)
- .putExtra(AuxioService.INTENT_KEY_NATIVE_START, true))
+ .putExtra(AuxioService.INTENT_KEY_START_ID, IntegerTable.START_ID_ACTIVITY))
if (!startIntentAction(intent)) {
// No intent action to do, just restore the previously saved state.
- playbackModel.playDeferred(DeferredPlayback.RestoreState)
+ playbackModel.playDeferred(DeferredPlayback.RestoreState(false))
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt
index c8d3ee145..0d32d20de 100644
--- a/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt
+++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt
@@ -107,7 +107,10 @@ class RoundedRectTransformation(
}
private fun calculateOutputSize(input: Bitmap, size: Size): Pair {
- // MODIFICATION: Remove short-circuiting for original size and input size
+ if (size == Size.ORIGINAL) {
+ // This path only runs w/the widget code, which already normalizes widget sizes
+ return input.width to input.height
+ }
val multiplier =
DecodeUtils.computeSizeMultiplier(
srcWidth = input.width,
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
index b8a3d2b26..7d60f825d 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
@@ -280,9 +280,6 @@ constructor(
}
logD("Registering worker $worker")
indexingWorker = worker
- if (indexingState == null) {
- worker.requestIndex(true)
- }
}
@Synchronized
diff --git a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheDatabase.kt b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheDatabase.kt
index 2a7113066..5ea7c8ebf 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheDatabase.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheDatabase.kt
@@ -32,7 +32,7 @@ import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.metadata.correctWhitespace
import org.oxycblt.auxio.music.metadata.splitEscaped
-@Database(entities = [CachedSong::class], version = 46, exportSchema = false)
+@Database(entities = [CachedSong::class], version = 49, exportSchema = false)
abstract class CacheDatabase : RoomDatabase() {
abstract fun cachedSongsDao(): CachedSongsDao
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt b/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt
index 3f5662b8d..30f4564c8 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt
@@ -70,9 +70,10 @@ sealed interface Name : Comparable {
final override fun compareTo(other: Name) =
when (other) {
is Known -> {
- val result = sortTokens.zip(other.sortTokens).fold(0) { acc, (token, otherToken) ->
- acc.takeIf { it != 0 } ?: token.compareTo(otherToken)
- }
+ val result =
+ sortTokens.zip(other.sortTokens).fold(0) { acc, (token, otherToken) ->
+ acc.takeIf { it != 0 } ?: token.compareTo(otherToken)
+ }
if (result != 0) result else sortTokens.size.compareTo(other.sortTokens.size)
}
is Unknown -> 1
diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt
index d30e5324e..b0e23b0a2 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt
@@ -153,6 +153,7 @@ private class TagWorkerImpl(
private fun populateWithId3v2(textFrames: Map>) {
// Song
+ logD(textFrames)
(textFrames["TXXX:musicbrainz release track id"]
?: textFrames["TXXX:musicbrainz_releasetrackid"])
?.let { rawSong.musicBrainzId = it.first() }
@@ -200,10 +201,13 @@ private class TagWorkerImpl(
(textFrames["TXXX:musicbrainz artist id"] ?: textFrames["TXXX:musicbrainz_artistid"])?.let {
rawSong.artistMusicBrainzIds = it
}
- (textFrames["TXXX:artists"] ?: textFrames["TPE1"])?.let { rawSong.artistNames = it }
+ (textFrames["TXXX:artists"] ?: textFrames["TPE1"] ?: textFrames["TXXX:artist"])?.let {
+ rawSong.artistNames = it
+ }
(textFrames["TXXX:artistssort"]
?: textFrames["TXXX:artists_sort"] ?: textFrames["TXXX:artists sort"]
- ?: textFrames["TSOP"])
+ ?: textFrames["TSOP"] ?: textFrames["artistsort"]
+ ?: textFrames["TXXX:artist sort"])
?.let { rawSong.artistSortNames = it }
// Album artist
@@ -212,13 +216,14 @@ private class TagWorkerImpl(
?.let { rawSong.albumArtistMusicBrainzIds = it }
(textFrames["TXXX:albumartists"]
?: textFrames["TXXX:album_artists"] ?: textFrames["TXXX:album artists"]
- ?: textFrames["TPE2"])
+ ?: textFrames["TPE2"] ?: textFrames["TXXX:albumartist"]
+ ?: textFrames["TXXX:album artist"])
?.let { rawSong.albumArtistNames = it }
(textFrames["TXXX:albumartistssort"]
?: textFrames["TXXX:albumartists_sort"] ?: textFrames["TXXX:albumartists sort"]
?: textFrames["TXXX:albumartistsort"]
// This is a non-standard iTunes extension
- ?: textFrames["TSO2"])
+ ?: textFrames["TSO2"] ?: textFrames["TXXX:album artist sort"])
?.let { rawSong.albumArtistSortNames = it }
// Genre
@@ -326,7 +331,8 @@ private class TagWorkerImpl(
}
(comments["artists"] ?: comments["artist"])?.let { rawSong.artistNames = it }
(comments["artistssort"]
- ?: comments["artists_sort"] ?: comments["artists sort"] ?: comments["artistsort"])
+ ?: comments["artists_sort"] ?: comments["artists sort"] ?: comments["artistsort"]
+ ?: comments["artist sort"])
?.let { rawSong.artistSortNames = it }
// Album artist
@@ -334,12 +340,12 @@ private class TagWorkerImpl(
rawSong.albumArtistMusicBrainzIds = it
}
(comments["albumartists"]
- ?: comments["album_artists"] ?: comments["album artists"]
- ?: comments["albumartist"])
+ ?: comments["album_artists"] ?: comments["album artists"] ?: comments["albumartist"]
+ ?: comments["album artist"])
?.let { rawSong.albumArtistNames = it }
(comments["albumartistssort"]
?: comments["albumartists_sort"] ?: comments["albumartists sort"]
- ?: comments["albumartistsort"])
+ ?: comments["albumartistsort"] ?: comments["album artist sort"])
?.let { rawSong.albumArtistSortNames = it }
// Genre
diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
index 6362def6b..571e96ca7 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
@@ -79,6 +79,12 @@ constructor(
foregroundListener = null
}
+ fun start() {
+ if (musicRepository.indexingState == null) {
+ requestIndex(true)
+ }
+ }
+
fun createNotification(post: (IndexerNotification?) -> Unit) {
val state = musicRepository.indexingState
if (state is IndexingState.Indexing) {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
index 058b33403..59ac16d95 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
@@ -163,10 +163,18 @@ class ExoPlaybackStateHolder(
is DeferredPlayback.RestoreState -> {
logD("Restoring playback state")
restoreScope.launch {
- persistenceRepository.readState()?.let {
- // Apply the saved state on the main thread to prevent code expecting
- // state updates on the main thread from crashing.
- withContext(Dispatchers.Main) { playbackManager.applySavedState(it, false) }
+ val state = persistenceRepository.readState()
+ withContext(Dispatchers.Main) {
+ if (state != null) {
+ // Apply the saved state on the main thread to prevent code expecting
+ // state updates on the main thread from crashing.
+ playbackManager.applySavedState(state, false)
+ if (action.play) {
+ playbackManager.playing(true)
+ }
+ } else if (action.fallback != null) {
+ playbackManager.playDeferred(action.fallback)
+ }
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt
index 69f2aab6d..ad5102477 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt
@@ -109,11 +109,23 @@ constructor(
}
}
- fun handleNonNativeStart() {
+ fun start(startedBy: Int) {
// At minimum we want to ensure an active playback state.
// TODO: Possibly also force to go foreground?
logD("Handling non-native start.")
- playbackManager.playDeferred(DeferredPlayback.RestoreState)
+ val action =
+ when (startedBy) {
+ IntegerTable.START_ID_ACTIVITY -> null
+ IntegerTable.START_ID_TASKER ->
+ DeferredPlayback.RestoreState(
+ play = true, fallback = DeferredPlayback.ShuffleAll)
+ // External services using Auxio better know what they are doing.
+ else -> DeferredPlayback.RestoreState(play = false)
+ }
+ if (action != null) {
+ logD("Initing service fragment using action $action")
+ playbackManager.playDeferred(action)
+ }
}
fun hasNotification(): Boolean = exoHolder.sessionOngoing
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateHolder.kt
index 857ac6898..c2c51f0ce 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateHolder.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateHolder.kt
@@ -276,7 +276,8 @@ data class QueueChange(val type: Type, val instructions: UpdateInstructions) {
/** Possible long-running background tasks handled by the background playback task. */
sealed interface DeferredPlayback {
/** Restore the previously saved playback state. */
- data object RestoreState : DeferredPlayback
+ data class RestoreState(val play: Boolean, val fallback: DeferredPlayback? = null) :
+ DeferredPlayback
/**
* Start shuffled playback of the entire music library. Analogous to the "Shuffle All" shortcut.
diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt b/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
new file mode 100644
index 000000000..1dd5c8997
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 Auxio Project
+ * Start.kt is part of Auxio.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.oxycblt.auxio.tasker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.content.ContextCompat
+import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoOutputOrInput
+import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
+import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutputOrInput
+import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
+import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
+import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult
+import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
+import org.oxycblt.auxio.AuxioService
+import org.oxycblt.auxio.IntegerTable
+import org.oxycblt.auxio.R
+
+class StartActionHelper(config: TaskerPluginConfig) :
+ TaskerPluginConfigHelperNoOutputOrInput(config) {
+ override val runnerClass: Class
+ get() = StartActionRunner::class.java
+
+ override fun addToStringBlurb(input: TaskerInput, blurbBuilder: StringBuilder) {
+ blurbBuilder.append(context.getString(R.string.lng_tasker_start))
+ }
+}
+
+class ActivityConfigStartAction : Activity(), TaskerPluginConfigNoInput {
+ override val context
+ get() = applicationContext
+
+ private val taskerHelper by lazy { StartActionHelper(this) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ taskerHelper.finishForTasker()
+ }
+}
+
+class StartActionRunner : TaskerPluginRunnerActionNoOutputOrInput() {
+ override fun run(context: Context, input: TaskerInput): TaskerPluginResult {
+ ContextCompat.startForegroundService(
+ context,
+ Intent(context, AuxioService::class.java)
+ .putExtra(AuxioService.INTENT_KEY_START_ID, IntegerTable.START_ID_TASKER))
+ while (!AuxioService.isForeground) {
+ Thread.sleep(100)
+ }
+ return TaskerPluginResultSucess()
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetBitmapTransformation.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetBitmapTransformation.kt
new file mode 100644
index 000000000..ac44e418d
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetBitmapTransformation.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2024 Auxio Project
+ * WidgetBitmapTransformation.kt is part of Auxio.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.oxycblt.auxio.widgets
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import coil.size.Size
+import coil.transform.Transformation
+import kotlin.math.sqrt
+
+class WidgetBitmapTransformation(private val reduce: Float) : Transformation {
+ private val metrics = Resources.getSystem().displayMetrics
+ private val sw = metrics.widthPixels
+ private val sh = metrics.heightPixels
+ // Cap memory usage at 1.5 times the size of the display
+ // 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
+ // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+ // Of course since OEMs randomly patch this check, we give a lot of slack.
+ private val maxBitmapArea = (1.5 * sw * sh / reduce).toInt()
+
+ override val cacheKey: String
+ get() = "WidgetBitmapTransformation:${maxBitmapArea}"
+
+ override suspend fun transform(input: Bitmap, size: Size): Bitmap {
+ if (size !== Size.ORIGINAL) {
+ // The widget loading stack basically discards the size parameter since there's no
+ // sane value from the get-go, all this transform does is actually dynamically apply
+ // the size cap so this transform must always be zero.
+ throw IllegalArgumentException("WidgetBitmapTransformation requires original size.")
+ }
+ val inputArea = input.width * input.height
+ if (inputArea != maxBitmapArea) {
+ val scale = sqrt(maxBitmapArea / inputArea.toDouble())
+ val newWidth = (input.width * scale).toInt()
+ val newHeight = (input.height * scale).toInt()
+ return Bitmap.createScaledBitmap(input, newWidth, newHeight, true)
+ }
+ return input
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt
index 09ac5e8f1..1e95eb6f4 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
+import coil.size.Size
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import org.oxycblt.auxio.R
@@ -96,24 +97,19 @@ constructor(
0
}
- return if (cornerRadius > 0) {
- // If rounded, reduce the bitmap size further to obtain more pronounced
- // rounded corners.
- builder.size(getSafeRemoteViewsImageSize(context, 10f))
- val cornersTransformation =
- RoundedRectTransformation(cornerRadius.toFloat())
+ val transformations = buildList {
if (imageSettings.forceSquareCovers) {
- builder.transformations(
- SquareCropTransformation.INSTANCE, cornersTransformation)
+ add(SquareCropTransformation.INSTANCE)
+ }
+ if (cornerRadius > 0) {
+ add(WidgetBitmapTransformation(15f))
+ add(RoundedRectTransformation(cornerRadius.toFloat()))
} else {
- builder.transformations(cornersTransformation)
+ add(WidgetBitmapTransformation(3f))
}
- } else {
- if (imageSettings.forceSquareCovers) {
- builder.transformations(SquareCropTransformation.INSTANCE)
- }
- builder.size(getSafeRemoteViewsImageSize(context))
}
+
+ return builder.size(Size.ORIGINAL).transformations(transformations)
}
override fun onCompleted(bitmap: Bitmap?) {
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt
index 799aa8a67..953af14c8 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt
@@ -27,7 +27,6 @@ import android.widget.RemoteViews
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
-import kotlin.math.sqrt
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.newMainPendingIntent
@@ -46,24 +45,6 @@ fun newRemoteViews(context: Context, @LayoutRes layoutRes: Int): RemoteViews {
return views
}
-/**
- * Get an image size guaranteed to not exceed the [RemoteViews] bitmap memory limit, assuming that
- * there is only one image.
- *
- * @param context [Context] required to perform calculation.
- * @param reduce Optional multiplier to reduce the image size. Recommended value is 2 to avoid
- * device-specific variations in memory limit.
- * @return The dimension of a bitmap that can be safely used in [RemoteViews].
- */
-fun getSafeRemoteViewsImageSize(context: Context, reduce: Float = 2f): Int {
- val metrics = context.resources.displayMetrics
- val sw = metrics.widthPixels
- val sh = metrics.heightPixels
- // Maximum size is 1/3 total screen area * 4 bytes per pixel. Reverse
- // that to obtain the image size.
- return sqrt((6f / 4f / reduce) * sw * sh).toInt()
-}
-
/**
* Set the background resource of a [RemoteViews] View.
*
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e4431dacd..886e5ce55 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -153,6 +153,7 @@
Shuffle
Shuffle all
+ Start playback
OK
Cancel
@@ -212,6 +213,10 @@
Donate to the project to get your name added here!
Search your library…
+
+ Starts Auxio using the previously saved state. If no saved state is available, all songs will be shuffled. Playback will start immediately.
+ \n\nWARNING: Be careful controlling this service, if you close it and then try to use it again, you will probably crash the app.
+
diff --git a/fastlane/metadata/android/en-US/changelogs/49.txt b/fastlane/metadata/android/en-US/changelogs/49.txt
new file mode 100644
index 000000000..62d3f517b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/49.txt
@@ -0,0 +1,3 @@
+Auxio 3.5.0 adds support for android auto alongside various playback and music quality of life improvements.
+This release adds basic Tasker integration while fixing a few issues that affected certain devices.
+For more information, see https://github.com/OxygenCobalt/Auxio/releases/tag/v3.5.3
diff --git a/media b/media
index 9fc2401b8..34b33175c 160000
--- a/media
+++ b/media
@@ -1 +1 @@
-Subproject commit 9fc2401b8fdc2b23905402462e775c6db4e1527f
+Subproject commit 34b33175c00183dc95cdcb8c735033b6785041e1