Merge branch 'hotfixes' into dev
This commit is contained in:
commit
f251813200
22 changed files with 251 additions and 70 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -15,6 +15,24 @@
|
|||
- Excessive CPU no longer spent showing music loading process
|
||||
- Fixed playback sheet flickering on warm start
|
||||
|
||||
## 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
|
||||
- Fixed music loading failure from improper sort systems (For real this time)
|
||||
|
||||
## 3.5.1
|
||||
|
||||
#### What's Fixed
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<h1 align="center"><b>Auxio</b></h1>
|
||||
<h4 align="center">A simple, rational music player for android.</h4>
|
||||
<p align="center">
|
||||
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.5.1">
|
||||
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.5.1&color=64B5F6&style=flat">
|
||||
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.5.3">
|
||||
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.5.3&color=64B5F6&style=flat">
|
||||
</a>
|
||||
<a href="https://github.com/oxygencobalt/Auxio/releases/">
|
||||
<img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg?color=4B95DE&style=flat">
|
||||
|
|
|
@ -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.1"
|
||||
versionCode 47
|
||||
versionName "3.5.3"
|
||||
versionCode 49
|
||||
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
|
@ -155,6 +155,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"
|
||||
|
|
|
@ -135,5 +135,15 @@
|
|||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
|
||||
<!-- Tasker 'start service' integration -->
|
||||
<activity
|
||||
android:name=".tasker.ActivityConfigStartAction"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/lbl_start_playback">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -55,13 +55,9 @@ class AuxioService : MediaLibraryService(), ForegroundListener {
|
|||
}
|
||||
|
||||
private fun start(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()
|
||||
}
|
||||
val startId = intent?.getIntExtra(INTENT_KEY_START_ID, -1) ?: -1
|
||||
indexingFragment.start()
|
||||
mediaSessionFragment.start(startId)
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
|
@ -87,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
|
||||
|
@ -95,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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,10 @@ class RoundedRectTransformation(
|
|||
}
|
||||
|
||||
private fun calculateOutputSize(input: Bitmap, size: Size): Pair<Int, Int> {
|
||||
// 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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -70,12 +70,12 @@ sealed interface Name : Comparable<Name> {
|
|||
final override fun compareTo(other: Name) =
|
||||
when (other) {
|
||||
is Known -> {
|
||||
// Progressively compare the sort tokens between each known name.
|
||||
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)
|
||||
}
|
||||
// Unknown names always come before known names.
|
||||
is Unknown -> 1
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ class TagInterpreterImpl @Inject constructor(private val coverExtractor: CoverEx
|
|||
|
||||
private fun populateWithId3v2(rawSong: RawSong, textFrames: Map<String, List<String>>) {
|
||||
// Song
|
||||
logD(textFrames)
|
||||
(textFrames["TXXX:musicbrainz release track id"]
|
||||
?: textFrames["TXXX:musicbrainz_releasetrackid"])
|
||||
?.let { rawSong.musicBrainzId = it.first() }
|
||||
|
@ -147,10 +148,13 @@ class TagInterpreterImpl @Inject constructor(private val coverExtractor: CoverEx
|
|||
(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
|
||||
|
@ -159,13 +163,14 @@ class TagInterpreterImpl @Inject constructor(private val coverExtractor: CoverEx
|
|||
?.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
|
||||
|
@ -273,7 +278,8 @@ class TagInterpreterImpl @Inject constructor(private val coverExtractor: CoverEx
|
|||
}
|
||||
(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
|
||||
|
@ -281,12 +287,12 @@ class TagInterpreterImpl @Inject constructor(private val coverExtractor: CoverEx
|
|||
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
|
||||
|
|
|
@ -164,10 +164,18 @@ class ExoPlaybackStateHolder(
|
|||
is DeferredPlayback.RestoreState -> {
|
||||
logD("Restoring playback state")
|
||||
restoreScope.launch {
|
||||
persistenceRepository.readState()?.let {
|
||||
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.
|
||||
withContext(Dispatchers.Main) { playbackManager.applySavedState(it, false) }
|
||||
playbackManager.applySavedState(state, false)
|
||||
if (action.play) {
|
||||
playbackManager.playing(true)
|
||||
}
|
||||
} else if (action.fallback != null) {
|
||||
playbackManager.playDeferred(action.fallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -281,7 +281,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.
|
||||
|
|
70
app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
Normal file
70
app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Unit>) :
|
||||
TaskerPluginConfigHelperNoOutputOrInput<StartActionRunner>(config) {
|
||||
override val runnerClass: Class<StartActionRunner>
|
||||
get() = StartActionRunner::class.java
|
||||
|
||||
override fun addToStringBlurb(input: TaskerInput<Unit>, 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<Unit>): TaskerPluginResult<Unit> {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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?) {
|
||||
|
|
|
@ -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 3 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 = 3f): 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.
|
||||
*
|
||||
|
|
|
@ -153,6 +153,7 @@
|
|||
<string name="lbl_shuffle_shortcut_short">Shuffle</string>
|
||||
<!-- Limit to 25 characters -->
|
||||
<string name="lbl_shuffle_shortcut_long">Shuffle all</string>
|
||||
<string name="lbl_start_playback">Start playback</string>
|
||||
|
||||
<string name="lbl_ok">OK</string>
|
||||
<string name="lbl_cancel">Cancel</string>
|
||||
|
@ -205,6 +206,10 @@
|
|||
<string name="lng_supporters_promo">Donate to the project to get your name added here!</string>
|
||||
<!-- As in music library -->
|
||||
<string name="lng_search_library">Search your library…</string>
|
||||
<string name="lng_tasker_start">
|
||||
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.
|
||||
</string>
|
||||
|
||||
<!-- Settings namespace | Settings-related labels -->
|
||||
<eat-comment />
|
||||
|
|
3
fastlane/metadata/android/en-US/changelogs/48.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/48.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
Auxio 3.5.0 adds support for android auto alongside various playback and music quality of life improvements.
|
||||
This release fixes a critical bug with the music loader.
|
||||
For more information, see https://github.com/OxygenCobalt/Auxio/releases/tag/v3.5.2
|
3
fastlane/metadata/android/en-US/changelogs/49.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/49.txt
Normal file
|
@ -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
|
2
media
2
media
|
@ -1 +1 @@
|
|||
Subproject commit 9fc2401b8fdc2b23905402462e775c6db4e1527f
|
||||
Subproject commit 34b33175c00183dc95cdcb8c735033b6785041e1
|
Loading…
Reference in a new issue