diff --git a/app/build.gradle b/app/build.gradle index fd4bfacc9..15206897f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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..564220423 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 6dc66bd76..f0352d523 100644 --- a/app/src/main/java/org/oxycblt/auxio/AuxioService.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioService.kt @@ -55,13 +55,9 @@ class AuxioService : MediaLibraryService(), ForegroundListener { } private fun onHandleForeground(intent: Intent?) { - val nativeStart = intent?.getBooleanExtra(INTENT_KEY_NATIVE_START, false) ?: false + val startId = intent?.getIntExtra(INTENT_KEY_START_ID, -1) ?: -1 indexingFragment.start() - if (!nativeStart) { - // Some foreign code started us, no guarantees about foreground stability. Figure - // out what to do. - mediaSessionFragment.handleNonNativeStart() - } + 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" } } 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/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 471a6498c..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 class RestoreState(val play: Boolean, val fallback: DeferredPlayback? = null) : 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..b146ec443 --- /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 + +class StartActionHelper(config: TaskerPluginConfig) : + TaskerPluginConfigHelperNoOutputOrInput(config) { + override val runnerClass: Class + get() = StartActionRunner::class.java + + override fun addToStringBlurb(input: TaskerInput, blurbBuilder: StringBuilder) { + blurbBuilder.append( + "Starts Auxio using the previously saved state. If no saved state is available, all songs will be shuffled. Playback will start immediately. Be careful controlling this service, if you close it and then try to use it again, you will probably crash the app.") + } +} + +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() + } +}