diff --git a/android/app/build.gradle b/android/app/build.gradle
index bf08564d..cb382c3d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -21,7 +21,11 @@ kotlin {
}
android {
- kotlinOptions {
+
+ buildFeatures {
+ viewBinding true
+ }
+ kotlinOptions {
freeCompilerArgs = ['-Xjvm-default=all']
}
compileSdkVersion rootProject.ext.compileSdkVersion
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e737fd2b..929d7ec5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,83 +1,94 @@
+ xmlns:dist="http://schemas.android.com/apk/distribution"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.audiobookshelf.app"
+ android:installLocation="preferExternal" >
-
-
+
+ android:usesCleartextTraffic="true" >
+
+
+
+
+
+
-
-
+
-
-
-
+ android:name="com.google.android.gms.car.notification.SmallIcon"
+ android:resource="@drawable/icon" />
+
+
+ android:label="@string/title_activity_main"
+ android:launchMode="singleTask"
+ android:theme="@style/AppTheme.NoActionBarLaunch" >
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/android/app/src/main/java/com/audiobookshelf/app/NewAppWidget.kt b/android/app/src/main/java/com/audiobookshelf/app/NewAppWidget.kt
new file mode 100644
index 00000000..c160ca06
--- /dev/null
+++ b/android/app/src/main/java/com/audiobookshelf/app/NewAppWidget.kt
@@ -0,0 +1,96 @@
+package com.audiobookshelf.app
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE
+import android.util.Log
+import android.widget.RemoteViews
+import androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent
+import com.audiobookshelf.app.device.DeviceManager
+import com.audiobookshelf.app.device.WidgetEventEmitter
+import com.audiobookshelf.app.player.PlayerNotificationService
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.AppWidgetTarget
+import com.bumptech.glide.request.transition.Transition
+
+/**
+ * Implementation of App Widget functionality.
+ */
+class NewAppWidget : AppWidgetProvider() {
+ val tag = "NewAppWidget"
+
+ override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
+ // There may be multiple widgets active, so update all of them
+ for (appWidgetId in appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId, null,false)
+ }
+ }
+
+ override fun onEnabled(context: Context) {
+ Log.w(tag, "onEnabled check context ${context.packageName}")
+
+ // Enter relevant functionality for when the first widget is created
+ DeviceManager.widgetUpdater = (object : WidgetEventEmitter {
+ override fun onPlayerChanged(pns:PlayerNotificationService) {
+ val isPlaying = pns.currentPlayer.isPlaying
+ Log.i(tag, "onPlayerChanged | Is Playing? $isPlaying")
+
+ val appWidgetManager = AppWidgetManager.getInstance(context)
+ val componentName = ComponentName(context, NewAppWidget::class.java)
+ val ids = appWidgetManager.getAppWidgetIds(componentName)
+
+ val playbackSession = pns.getCurrentPlaybackSessionCopy()
+ val cover = playbackSession?.getCoverUri()
+
+ for (widgetId in ids) {
+ updateAppWidget(context, appWidgetManager, widgetId, cover, isPlaying)
+ }
+ }
+ })
+ }
+
+ override fun onDisabled(context: Context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+}
+
+internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, coverUri:Uri?, isPlaying:Boolean) {
+
+ val views = RemoteViews(context.packageName, R.layout.new_app_widget)
+
+ val playPausePI = buildMediaButtonPendingIntent(context, ACTION_PLAY_PAUSE)
+ views.setOnClickPendingIntent(R.id.playPauseIcon, playPausePI)
+
+ val wholeWidgetClickI = Intent(context, MainActivity::class.java)
+ wholeWidgetClickI.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ val wholeWidgetClickPI = PendingIntent.getActivity(
+ context,
+ System.currentTimeMillis().toInt(),
+ wholeWidgetClickI,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ views.setOnClickPendingIntent(R.id.appWidget, wholeWidgetClickPI)
+
+ val imageUri = coverUri ?: Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
+ val awt: AppWidgetTarget = object : AppWidgetTarget(context.applicationContext, R.id.imageView, views, appWidgetId) {
+ override fun onResourceReady(resource: Bitmap, transition: Transition?) {
+ super.onResourceReady(resource, transition)
+ }
+ }
+
+ val options = RequestOptions().override(300, 300).placeholder(R.drawable.icon).error(R.drawable.icon)
+ Glide.with(context.applicationContext).asBitmap().load(imageUri).apply(options).into(awt)
+
+ val playPauseResource = if (isPlaying) R.drawable.ic_media_pause_dark else R.drawable.ic_media_play_dark
+ views.setImageViewResource(R.id.playPauseIcon, playPauseResource)
+
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
index d233ffc4..ef6b65cd 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt
@@ -1,9 +1,12 @@
package com.audiobookshelf.app.device
import android.util.Log
-import com.audiobookshelf.app.data.DbManager
-import com.audiobookshelf.app.data.DeviceData
-import com.audiobookshelf.app.data.ServerConnectionConfig
+import com.audiobookshelf.app.data.*
+import com.audiobookshelf.app.player.PlayerNotificationService
+
+interface WidgetEventEmitter {
+ fun onPlayerChanged(pns:PlayerNotificationService)
+}
object DeviceManager {
const val tag = "DeviceManager"
@@ -19,6 +22,8 @@ object DeviceManager {
val isConnectedToServer get() = serverConnectionConfig != null
val hasLastServerConnectionConfig get() = deviceData.getLastServerConnectionConfig() != null
+ var widgetUpdater:WidgetEventEmitter? = null
+
init {
Log.d(tag, "Device Manager Singleton invoked")
}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
index d23f7451..3809286d 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
@@ -132,8 +132,25 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
}
fun handleCallMediaButton(intent: Intent): Boolean {
+ Log.w(tag, "handleCallMediaButton $intent | ${intent.action}")
+
if(Intent.ACTION_MEDIA_BUTTON == intent.action) {
val keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
+ Log.d(tag, "handleCallMediaButton keyEvent = $keyEvent | action ${keyEvent?.action}")
+
+ if (keyEvent?.action == KeyEvent.ACTION_DOWN) {
+ Log.d(tag, "handleCallMediaButton: key action_down for ${keyEvent.keyCode}")
+ when (keyEvent.keyCode) {
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
+ Log.d(tag, "handleCallMediaButton: Media Play/Pause")
+ if (playerNotificationService.mPlayer.isPlaying) {
+ playerNotificationService.pause()
+ } else {
+ playerNotificationService.play()
+ }
+ }
+ }
+ }
if (keyEvent?.action == KeyEvent.ACTION_UP) {
Log.d(tag, "handleCallMediaButton: key action_up for ${keyEvent.keyCode}")
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
index 302abf33..239ebb97 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
@@ -118,6 +118,8 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) :
}
playerNotificationService.clientEventEmitter?.onPlayingUpdate(player.isPlaying)
+
+ DeviceManager.widgetUpdater?.onPlayerChanged(playerNotificationService)
}
}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
index 68cdd8ec..2cbd9c35 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
@@ -210,6 +210,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
.apply {
setSessionActivity(sessionActivityPendingIntent)
isActive = true
+ setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
}
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
@@ -768,7 +769,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private val ANDROID_WEARABLE_PKG_NAME = "com.google.android.wearable.app"
private val ANDROID_GSEARCH_PKG_NAME = "com.google.android.googlequicksearchbox"
private val ANDROID_AUTOMOTIVE_PKG_NAME = "com.google.android.carassistant"
- private val VALID_MEDIA_BROWSERS = mutableListOf(ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
+ private val VALID_MEDIA_BROWSERS = mutableListOf("com.audiobookshelf.app", "com.android.systemui", ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
private val AUTO_MEDIA_ROOT = "/"
private val ALL_ROOT = "__ALL__"
diff --git a/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png b/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png
new file mode 100644
index 00000000..894b069a
Binary files /dev/null and b/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png differ
diff --git a/android/app/src/main/res/drawable-v21/app_widget_background.xml b/android/app/src/main/res/drawable-v21/app_widget_background.xml
new file mode 100644
index 00000000..878e0414
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/app_widget_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml b/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml
new file mode 100644
index 00000000..cd72ec57
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/new_app_widget.xml b/android/app/src/main/res/layout/new_app_widget.xml
new file mode 100644
index 00000000..99ac1b46
--- /dev/null
+++ b/android/app/src/main/res/layout/new_app_widget.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values-v21/styles.xml b/android/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 00000000..80d515ff
--- /dev/null
+++ b/android/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..7781ac86
--- /dev/null
+++ b/android/app/src/main/res/values/attrs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..b2bffa8d
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+ #FFE1F5FE
+ #FF81D4FA
+ #FF039BE5
+ #FF01579B
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..4db8c590
--- /dev/null
+++ b/android/app/src/main/res/values/dimens.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ 0dp
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index a99df0e9..b0944308 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -4,4 +4,6 @@
audiobookshelf
com.audiobookshelf.app
com.audiobookshelf.app
+ Add widget
+ Simple widget for audiobookshelf playback
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 102f1273..ccd013a3 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -19,4 +19,13 @@
+
+
+
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..254f4a55
--- /dev/null
+++ b/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/xml/new_app_widget_info.xml b/android/app/src/main/res/xml/new_app_widget_info.xml
new file mode 100644
index 00000000..ffa76ee7
--- /dev/null
+++ b/android/app/src/main/res/xml/new_app_widget_info.xml
@@ -0,0 +1,12 @@
+
+