widgets: backport to <v31

Modify the minimal wdiget so that it can work on Android 11 and below.
Was not actually that hard, mostly changing layouts around all things
considered. This also splits up the default view and the main widget
views, which makes managing UI state much easier.
This commit is contained in:
OxygenCobalt 2021-08-02 08:57:35 -06:00
parent ffebbc2839
commit 8673995630
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
18 changed files with 136 additions and 82 deletions

View file

@ -57,9 +57,9 @@
android:roundIcon="@mipmap/ic_launcher_round" />
<receiver
android:label="@string/info_widget_minimal"
android:name=".widgets.MinimalWidgetProvider"
android:exported="false"
android:enabled="@bool/widgets_supported">
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

View file

@ -20,7 +20,6 @@ import org.oxycblt.auxio.ui.isNight
/**
* The single [AppCompatActivity] for Auxio.
* TODO: Port widgets to non-12 android
* TODO: Fix intent issues
*/
class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels()

View file

@ -78,7 +78,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
private val settingsManager = SettingsManager.getInstance()
// Widgets
private val minimalWidget = MinimalWidgetProvider.new()
private val minimalWidget = MinimalWidgetProvider()
// State
private var isForeground = false
@ -179,6 +179,10 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
audioReactor.release()
releaseWakelock()
// Technically the widgets don't *have* to be reset, but any commands from them
// won't work if the service is dead, so we do it anyway
minimalWidget.stop(this)
playbackManager.removeCallback(this)
settingsManager.removeCallback(this)
@ -246,14 +250,14 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
this, song, settingsManager.colorizeNotif, ::startForegroundOrNotify
)
minimalWidget?.update(this, playbackManager)
minimalWidget.update(this, playbackManager)
return
}
// Clear if there's nothing to play.
player.stop()
minimalWidget?.stop(this)
minimalWidget.stop(this)
stopForegroundAndNotification()
}

View file

@ -11,6 +11,7 @@ import android.widget.RemoteViews
import androidx.annotation.LayoutRes
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -112,11 +113,3 @@ abstract class BaseWidget : AppWidgetProvider() {
const val KEY_WIDGET_TYPE = BuildConfig.APPLICATION_ID + ".key.WIDGET_TYPE"
}
}
// PLAN:
// - Service calls methods on the widgets, think a subset of PlaybackStateManager.Callback
// - Widgets send BACK broadcasts to control playback, as other parts of the code do
// - Since widgets are broadcastrecievers, this is okay, shouldn't cause memory leaks
// - Can't use playbackstatemanager here since that would make the app unkillable
// - Callbacks also need to handle PlaybackService dying and being unable to send any
// more updates, and PlaybackService starting up as well

View file

@ -2,7 +2,6 @@ package org.oxycblt.auxio.widgets
import android.content.Context
import android.os.Build
import android.view.View
import android.widget.RemoteViews
import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.loadBitmap
@ -17,14 +16,7 @@ class MinimalWidgetProvider : BaseWidget() {
override val type: Int get() = TYPE
override fun getDefaultViews(context: Context): RemoteViews {
val views = getRemoteViews(context, LAYOUT)
views.setInt(R.id.widget_cover, "setImageResource", R.drawable.ic_song_clear)
views.setInt(R.id.widget_cover, "setVisibility", View.VISIBLE)
views.setInt(R.id.widget_placeholder_msg, "setVisibility", View.VISIBLE)
views.setInt(R.id.widget_meta, "setVisibility", View.GONE)
return views
return getRemoteViews(context, R.layout.widget_default)
}
override fun updateViews(
@ -32,15 +24,12 @@ class MinimalWidgetProvider : BaseWidget() {
playbackManager: PlaybackStateManager,
onDone: (RemoteViews) -> Unit
) {
val views = getRemoteViews(context, LAYOUT)
val song = playbackManager.song
if (song != null) {
logD("updating view to ${song.name}")
// Show the proper widget views
views.setInt(R.id.widget_placeholder_msg, "setVisibility", View.GONE)
views.setInt(R.id.widget_meta, "setVisibility", View.VISIBLE)
val views = getRemoteViews(context, R.layout.widget_minimal)
// Update the metadata
views.setTextViewText(R.id.widget_song, song.name)
@ -51,24 +40,18 @@ class MinimalWidgetProvider : BaseWidget() {
if (bitmap != null) {
views.setBitmap(R.id.widget_cover, "setImageBitmap", bitmap)
} else {
views.setInt(R.id.widget_cover, "setImageResource", R.drawable.ic_song_clear)
views.setInt(R.id.widget_cover, "setImageResource", R.drawable.ic_song)
}
onDone(views)
}
} else {
views.setInt(R.id.widget_cover, "setImageResource", R.drawable.ic_song_clear)
onDone(views)
onDone(getDefaultViews(context))
}
}
companion object {
const val TYPE = 0xA0D0
// Workaround to make studio shut up about perfectly valid layouts somehow
// being invalid for remote views.
const val LAYOUT = R.layout.widget_minimal
fun new(): MinimalWidgetProvider? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MinimalWidgetProvider()

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners />
<solid android:color="@color/surface_black" />
</shape>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners />
<solid android:color="@color/surface_day" />
</shape>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners />
<solid android:color="@color/surface_night" />
</shape>

View file

@ -15,7 +15,7 @@
android:layout_weight="1"
android:scaleType="centerCrop"
tools:ignore="contentDescription"
tools:src="@drawable/ic_song_clear" />
tools:src="@drawable/ic_song" />
<android.widget.LinearLayout
android:id="@+id/widget_meta"
@ -31,6 +31,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?android:attr/textColorPrimary"
@ -42,6 +43,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textColor="?android:attr/textColorSecondary"
@ -49,14 +51,4 @@
</android.widget.LinearLayout>
<android.widget.TextView
android:id="@+id/widget_placeholder_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_medium"
android:text="@string/placeholder_playback"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold" />
</LinearLayout>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:theme="@style/Theme.Widget">
<android.widget.ImageView
android:id="@+id/widget_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.3"
android:scaleType="centerCrop"
tools:ignore="contentDescription"
android:src="@drawable/ic_song" />
<android.widget.TextView
android:id="@+id/widget_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:fontFamily="@font/inter"
android:padding="@dimen/spacing_medium"
android:text="@string/placeholder_playback"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold" />
</FrameLayout>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:orientation="vertical"
android:theme="@style/Theme.Widget">
<android.widget.ImageView
android:id="@+id/widget_cover"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
tools:ignore="contentDescription"
tools:src="@drawable/ic_song" />
<android.widget.LinearLayout
android:id="@+id/widget_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/colorBackground"
android:orientation="vertical"
android:padding="@dimen/spacing_medium">
<android.widget.TextView
android:id="@+id/widget_song"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
tools:text="Song Name" />
<android.widget.TextView
android:id="@+id/widget_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textColor="?android:attr/textColorSecondary"
tools:text="Artist Name" />
</android.widget.LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Widget" parent="@android:style/Theme.DeviceDefault.DayNight">
<!-- I really don't care enough to make Auxio's theming correspond to widget theming.
It doesn't happen on Android 12 anyway, so why bother backporting it when its so
useless. -->
<item name="colorPrimary">?android:attr/colorAccent</item>
<item name="colorSecondary">?android:attr/colorAccent</item>
<item name="colorSurface">@color/surface_color</item>
<item name="android:windowBackground">?attr/colorSurface</item>
<item name="android:colorBackground">?attr/colorSurface</item>
</style>
</resources>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Widgets are limited to android 12 until I can find an okay-ish way to backport them.
-->
<bool name="widgets_supported">true</bool>
</resources>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Widget" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="colorPrimary">?android:attr/colorAccent</item>
<item name="colorSecondary">?android:attr/colorAccent</item>
</style>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="widgets_supported">false</bool>
</resources>

View file

@ -4,6 +4,7 @@
<!-- Info namespace | App labels -->
<string name="info_app_desc">A simple, rational music player for android.</string>
<string name="info_channel_name">Music Playback</string>
<string name="info_widget_minimal">Minimal</string>
<!-- Label Namespace | Static Labels -->
<string name="label_retry">Retry</string>

View file

@ -42,4 +42,10 @@
<item name="colorSurface">@color/surface_black</item>
<item name="materialAlertDialogTheme">@style/Theme.CustomDialog.Black</item>
</style>
<!--
Theming widgets is technically possible below Android 10, but its too much of a hassle.
Default to a light blue theme.
-->
<style name="Theme.Widget" parent="@style/Theme.Blue" />
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_minimal"
android:minWidth="110dp"
android:minHeight="110dp"
android:minResizeWidth="110dp"
android:minResizeHeight="110dp"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="0"
android:widgetCategory="home_screen">
</appwidget-provider>