Skip to content

Commit f1d2e84

Browse files
committed
fix handful of crashers due to "maybe or maybe not" foreground service states.
- MidiDeviceServices cannot be loaded as FGS so they work non-FGS. - call exitProcess(0) only at "FGS has most likely terminated" state (hack). A remaining FGS issue is that launching a plugin instance on GenericPluginManagerMain() does not seem to trigger startForeground(). We either need some investigation or drastically change the API structure (V4?).
1 parent 21179f6 commit f1d2e84

File tree

7 files changed

+58
-21
lines changed

7 files changed

+58
-21
lines changed

androidaudioplugin-ui-compose-app/src/main/java/org/androidaudioplugin/ui/compose/app/GenericPluginManagerMain.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import androidx.navigation.compose.NavHost
2929
import androidx.navigation.compose.composable
3030
import androidx.navigation.compose.rememberNavController
3131
import androidx.navigation.navArgument
32+
import kotlinx.coroutines.GlobalScope
33+
import kotlinx.coroutines.currentCoroutineContext
34+
import kotlinx.coroutines.delay
35+
import kotlinx.coroutines.launch
3236
import org.androidaudioplugin.AudioPluginServiceHelper
3337
import org.androidaudioplugin.hosting.AudioPluginHostHelper
3438
import kotlin.system.exitProcess
@@ -104,7 +108,12 @@ fun GenericPluginManagerMain(scope: PluginManagerScope, listTitleBarText: String
104108
BackHandler {
105109
if (navController.currentBackStackEntry?.destination?.route == "plugin_list") {
106110
if (System.currentTimeMillis() - lastBackPressed < 2000) {
107-
exitProcess(0)
111+
(context as Activity).finish()
112+
// FIXME: this should ensure that foreground services all stopped, instead of hacky delays.
113+
GlobalScope.launch {
114+
delay(5000)
115+
exitProcess(0)
116+
}
108117
}
109118
else
110119
Toast.makeText(context, "Tap once more to quit", Toast.LENGTH_SHORT).show()

androidaudioplugin/src/main/cpp/android/android-application-context.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ NonRealtimeLoopRunner* get_non_rt_loop_runner() { return non_rt_loop_runner.get(
3838

3939
#include <pthread.h>
4040
void prepare_non_rt_event_looper() {
41+
assert(!non_rt_loop_runner);
42+
4143
auto looper = ALooper_prepare(0);
4244
ALooper_acquire(looper);
4345
// probably we need this constant adjustable...
@@ -53,6 +55,8 @@ void start_non_rt_event_looper() {
5355
}
5456

5557
void stop_non_rt_event_looper() {
58+
if (!non_rt_loop_runner)
59+
return; // already stopped
5660
auto looper = (ALooper*) non_rt_loop_runner->getLooper();
5761
ALooper_release(looper);
5862
non_rt_loop_runner.reset(nullptr);

androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginService.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.androidaudioplugin
22

3+
import android.app.ForegroundServiceStartNotAllowedException
34
import android.app.Notification
45
import android.app.NotificationChannel
56
import android.app.NotificationManager
67
import android.app.Service
78
import android.content.ComponentName
89
import android.content.Context
910
import android.content.Intent
11+
import android.os.Build
1012
import android.os.IBinder
1113
import android.os.Looper
1214
import androidx.core.app.NotificationCompat
@@ -54,8 +56,8 @@ open class AudioPluginService : Service()
5456
private var nativeBinder : IBinder? = null
5557
var extensions = mutableListOf<Extension>()
5658
private val notificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
57-
private val NOTIFICATION_ID = 1
5859
private val CHANNEL_ID by lazy { applicationInfo.packageName + ":" + javaClass.name }
60+
private val NOTIFICATION_ID by lazy { CHANNEL_ID.hashCode() }
5961

6062
override fun onCreate() {
6163
initialize(this)
@@ -73,11 +75,18 @@ open class AudioPluginService : Service()
7375
}
7476
}
7577

78+
override fun onDestroy() {
79+
if (foregroundServiceType != 0)
80+
stopForeground(STOP_FOREGROUND_REMOVE)
81+
82+
super.onDestroy()
83+
}
84+
7685
private var eventProcessorLooper: Looper? = null
7786

7887
// Depending on the plugin, it may be missing foreground service type in the manifest.
7988
// To avoid crash due to insufficient permission, we make foreground behavior optional.
80-
private val declaredForegroundServiceType by lazy { packageManager.getServiceInfo(ComponentName(applicationContext, javaClass.name), 0).foregroundServiceType }
89+
private val declaredForegroundServiceType by lazy { AudioPluginServiceHelper.getForegroundServiceType(this, packageName, javaClass.name) }
8190

8291
override fun onBind(intent: Intent?): IBinder? {
8392
val existing = nativeBinder
@@ -95,13 +104,16 @@ open class AudioPluginService : Service()
95104
this.start()
96105
}
97106

98-
if (declaredForegroundServiceType != 0)
99-
startForegroundService()
107+
maybeStartForegroundService()
100108

101109
return nativeBinder
102110
}
103111

104112
override fun onUnbind(intent: Intent?): Boolean {
113+
114+
if (foregroundServiceType != 0)
115+
stopForeground(STOP_FOREGROUND_REMOVE)
116+
105117
for(ext in extensions)
106118
ext.cleanup()
107119

@@ -115,9 +127,6 @@ open class AudioPluginService : Service()
115127
AudioPluginNatives.destroyBinderForService(binder)
116128
nativeBinder = null
117129

118-
if (declaredForegroundServiceType != 0)
119-
stopForeground(STOP_FOREGROUND_REMOVE)
120-
121130
return true
122131
}
123132

@@ -131,9 +140,15 @@ open class AudioPluginService : Service()
131140
notificationManager.createNotificationChannel(channel)
132141
}
133142

134-
private fun startForegroundService() {
135-
val notification = createNotification()
136-
startForeground(NOTIFICATION_ID, notification, declaredForegroundServiceType)
143+
private fun maybeStartForegroundService() {
144+
if (declaredForegroundServiceType != 0) {
145+
val notification = createNotification()
146+
try {
147+
startForeground(NOTIFICATION_ID, notification, declaredForegroundServiceType)
148+
} catch (ex: Exception) {
149+
android.util.Log.e("AAP.Hosting", "Failed to start as foreground service", ex)
150+
}
151+
}
137152
}
138153

139154
private fun createNotification(): Notification {

androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginServiceHelper.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.androidaudioplugin
22

3+
import android.content.ComponentName
34
import android.content.Context
45
import android.view.View
56
import org.androidaudioplugin.hosting.AudioPluginHostHelper
@@ -10,6 +11,10 @@ object AudioPluginServiceHelper {
1011
AudioPluginHostHelper.queryAudioPluginServices(context)
1112
.first { svc -> svc.packageName == context.packageName }
1213

14+
@JvmStatic
15+
fun getForegroundServiceType(context: Context, packageName: String, serviceClassName: String) =
16+
context.packageManager.getServiceInfo(ComponentName(packageName, serviceClassName), 0).foregroundServiceType
17+
1318
@JvmStatic
1419
external fun getServiceInstance(pluginId: String): Long
1520

androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginClientBase.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import android.media.AudioManager
55
import org.androidaudioplugin.AudioPluginNatives
66
import org.androidaudioplugin.PluginInformation
77

8-
open class AudioPluginClientBase(private val applicationContext: Context) {
8+
open class AudioPluginClientBase(private val context: Context) {
99
// Service connection
10-
protected val serviceConnector by lazy { AudioPluginServiceConnector(applicationContext) }
10+
protected val serviceConnector by lazy { AudioPluginServiceConnector(context) }
1111
protected val native by lazy { NativePluginClient.createFromConnection(serviceConnector.serviceConnectionId) }
1212

1313
val serviceConnectionId by lazy { serviceConnector.serviceConnectionId }
@@ -26,7 +26,7 @@ open class AudioPluginClientBase(private val applicationContext: Context) {
2626
suspend fun connectToPluginService(packageName: String) : PluginServiceConnection {
2727
val conn = serviceConnector.findExistingServiceConnection(packageName)
2828
if (conn == null) {
29-
val service = AudioPluginHostHelper.queryAudioPluginServices(applicationContext)
29+
val service = AudioPluginHostHelper.queryAudioPluginServices(context.applicationContext)
3030
.first { c -> c.packageName == packageName }
3131
return serviceConnector.bindAudioPluginService(service)
3232
}
@@ -48,9 +48,9 @@ open class AudioPluginClientBase(private val applicationContext: Context) {
4848
var sampleRate : Int
4949

5050
init {
51-
AudioPluginNatives.initializeAAPJni(applicationContext)
51+
AudioPluginNatives.initializeAAPJni(context.applicationContext)
5252

53-
val audioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
53+
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
5454
sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)?.toInt() ?: 44100
5555
}
5656
}

androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginHostHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ object AudioPluginHostHelper {
245245
fun ensureBinderConnected(servicePackageName: String, connector: AudioPluginServiceConnector) {
246246
ensureBinderConnected(
247247
queryAudioPluginService(
248-
connector.applicationContext,
248+
connector.context,
249249
servicePackageName
250250
), connector
251251
)

androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginServiceConnector.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.androidaudioplugin.hosting
22

3+
import android.app.Activity
34
import android.content.ComponentName
45
import android.content.Context
56
import android.content.Intent
@@ -9,6 +10,7 @@ import android.util.Log
910
import org.androidaudioplugin.AudioPluginException
1011
import org.androidaudioplugin.AudioPluginNatives
1112
import org.androidaudioplugin.AudioPluginService
13+
import org.androidaudioplugin.AudioPluginServiceHelper
1214
import org.androidaudioplugin.PluginServiceInformation
1315
import kotlin.coroutines.resume
1416
import kotlin.coroutines.suspendCoroutine
@@ -18,7 +20,7 @@ import kotlin.coroutines.suspendCoroutine
1820
1921
Native hosts also use this class to instantiate plugins and manage them.
2022
*/
21-
class AudioPluginServiceConnector(val applicationContext: Context) : AutoCloseable {
23+
class AudioPluginServiceConnector(val context: Context) : AutoCloseable {
2224
/*
2325
The ServiceConnection implementation class for AudioPluginService.
2426
*/
@@ -80,8 +82,10 @@ class AudioPluginServiceConnector(val applicationContext: Context) : AutoCloseab
8082
"AudioPluginHost",
8183
"bindAudioPluginService: ${service.packageName} | ${service.className}"
8284
)
83-
assert(applicationContext.startForegroundService(intent) != null)
84-
assert(applicationContext.bindService(intent, conn, Context.BIND_AUTO_CREATE))
85+
// start as FGS only if it is applicable
86+
if (context is Activity)
87+
assert(context.startForegroundService(intent) != null)
88+
assert(context.bindService(intent, conn, Context.BIND_AUTO_CREATE))
8589
}
8690

8791
private fun registerNewConnection(serviceConnection: ServiceConnection, serviceInfo: PluginServiceInformation, binder: IBinder) : PluginServiceConnection {
@@ -120,7 +124,7 @@ class AudioPluginServiceConnector(val applicationContext: Context) : AutoCloseab
120124
conn.serviceInfo.packageName,
121125
conn.serviceInfo.className
122126
)
123-
applicationContext.unbindService(conn.platformServiceConnection)
127+
context.unbindService(conn.platformServiceConnection)
124128
}
125129

126130
override fun close() {

0 commit comments

Comments
 (0)