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.

- - Latest Version + + Latest Version Releases 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