about: fix link opening not working on android 11

Auxio would usually open links by trying to walk through the app
chooser/default app situation themselves, mostly for compat purposes.
This method was not only broken in Android 11 with the addition of
the QUERY_ALL_PACKAGES permission, but it also seems to be made
obsolete since the android system seems to handle the app choser
pretty well in 11+. As a result, we replace the link opening code on
that version with a plain startActivity call and keep the old compat
code for older versions.

Resolves #47.
This commit is contained in:
OxygenCobalt 2021-08-27 18:28:37 -06:00
parent 792cc22583
commit b9658c698b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 71 additions and 52 deletions

View file

@ -20,8 +20,10 @@ package org.oxycblt.auxio.playback.queue
import android.graphics.Canvas
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R
import org.oxycblt.auxio.playback.PlaybackViewModel
import kotlin.math.abs
@ -32,15 +34,15 @@ import kotlin.math.sign
/**
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
* and [QueueAdapter] simultaneously.
* @param onItemScroll Callback for when an item begins to scroll off-screen. Argument passed
* is the dY value.
* @author OxygenCobalt
*/
class QueueDragCallback(
private val playbackModel: PlaybackViewModel,
private val onItemScroll: (Int) -> Unit
private val coordinator: CoordinatorLayout,
private val appBar: AppBarLayout
) : ItemTouchHelper.Callback() {
private lateinit var queueAdapter: QueueAdapter
private val tConsumed = IntArray(2)
private var shouldLift = true
override fun getMovementFlags(
@ -79,24 +81,18 @@ class QueueDragCallback(
val result = clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
recyclerView.post {
onItemScroll(result)
// CoordinatorLayout refuses to propagate a scroll event initiated by an item scroll,
// so we do it ourselves.
(appBar.layoutParams as CoordinatorLayout.LayoutParams).behavior
?.onNestedPreScroll(
coordinator, appBar, recyclerView,
0, result, tConsumed, 0
)
}
return result
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return playbackModel.moveQueueDataItems(
viewHolder.bindingAdapterPosition,
target.bindingAdapterPosition,
queueAdapter
)
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
@ -107,10 +103,12 @@ class QueueDragCallback(
isCurrentlyActive: Boolean
) {
// The material design page on elevation has a cool example of draggable items elevating
// themselves when being dragged. Too bad google doesn't provide a single utility to do
// this in your own app :^). To emulate it, I check if this child is in a drag state and
// themselves when being dragged. Too bad google's implementation of this doesn't even
// work :^). To emulate it on my own, I check if this child is in a drag state and
// then animate an elevation change. This animation also changes the background so that
// the item will actually draw over.
// TODO: Maybe restrict the item from being drawn over the recycler bounds?
// Seems like its possible with enough UI magic
val view = viewHolder.itemView
@ -129,7 +127,7 @@ class QueueDragCallback(
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
// When an elevated item is done, we reset the elevation using another animation
// When an elevated item is cleared, we reset the elevation using another animation
// and set the background to null again so a seam doesn't show up in further actions.
val view = viewHolder.itemView
@ -148,6 +146,18 @@ class QueueDragCallback(
super.clearView(recyclerView, viewHolder)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return playbackModel.moveQueueDataItems(
viewHolder.bindingAdapterPosition,
target.bindingAdapterPosition,
queueAdapter
)
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter)
}

View file

@ -24,7 +24,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@ -53,16 +52,11 @@ class QueueFragment : Fragment() {
): View {
val binding = FragmentQueueBinding.inflate(inflater)
val callback = QueueDragCallback(playbackModel) { dY ->
// By default, CoordinatorLayout is not aware of scroll events originating from
// when an item is scrolled off-screen, so we manually add a scroll event ourselves.
(binding.queueAppbar.layoutParams as CoordinatorLayout.LayoutParams).behavior
?.onNestedPreScroll(
binding.queueCoordinator, binding.queueAppbar,
binding.queueRecycler, 0, dY,
IntArray(2), 0
)
}
val callback = QueueDragCallback(
playbackModel,
binding.queueCoordinator,
binding.queueAppbar
)
val helper = ItemTouchHelper(callback)
val queueAdapter = QueueAdapter(helper, playbackModel)
@ -110,7 +104,6 @@ class QueueFragment : Fragment() {
if (isShuffling != lastShuffle) {
lastShuffle = isShuffling
binding.queueRecycler.scrollBy(0, 100)
binding.queueRecycler.scrollToPosition(0)
}
}
@ -120,8 +113,8 @@ class QueueFragment : Fragment() {
private fun setupEdgeForQueue(binding: FragmentQueueBinding) {
if (isEdgeOn()) {
// Account for the side navigation bar if required.
binding.root.setOnApplyWindowInsetsListener { v, insets ->
// Account for the side navigation bar if required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val bars = insets.getInsets(WindowInsets.Type.systemBars())

View file

@ -21,6 +21,7 @@ package org.oxycblt.auxio.settings
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -74,27 +75,43 @@ class AboutFragment : Fragment() {
Intent.FLAG_ACTIVITY_NEW_TASK
)
val pkgName = requireContext().packageManager.resolveActivity(
browserIntent, PackageManager.MATCH_DEFAULT_ONLY
)?.activityInfo?.packageName
if (pkgName != null) {
if (pkgName == "android") {
// No default browser [Must open app chooser, may not be supported]
openAppChooser(browserIntent)
} else {
try {
browserIntent.setPackage(pkgName)
startActivity(browserIntent)
} catch (exception: ActivityNotFoundException) {
// Not browser but an app chooser due to OEM garbage
browserIntent.setPackage(null)
openAppChooser(browserIntent)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 seems to now handle the app chooser situations on its own now
// [along with adding a new permission that breaks the old manual code], so
// we just do a typical activity launch.
try {
requireContext().startActivity(browserIntent)
} catch (e: ActivityNotFoundException) {
// No app installed to open the link
requireContext().showToast(R.string.err_no_app)
}
} else {
// No app installed to open the link
requireContext().showToast(R.string.err_no_app)
// On older versions of android, opening links from an ACTION_VIEW intent might
// not work in all cases, especially when no default app was set. If that is the
// case, we will try to manually handle these cases before we try to launch the
// browser.
val pkgName = requireContext().packageManager.resolveActivity(
browserIntent, PackageManager.MATCH_DEFAULT_ONLY
)?.activityInfo?.packageName
if (pkgName != null) {
if (pkgName == "android") {
// No default browser [Must open app chooser, may not be supported]
openAppChooser(browserIntent)
} else {
try {
browserIntent.setPackage(pkgName)
startActivity(browserIntent)
} catch (e: ActivityNotFoundException) {
// Not browser but an app chooser due to OEM garbage
browserIntent.setPackage(null)
openAppChooser(browserIntent)
}
}
} else {
// No app installed to open the link
requireContext().showToast(R.string.err_no_app)
}
}
}

View file

@ -13,5 +13,4 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele
- Audio Focus / Headset Management
- No internet connectivity whatsoever
- Kotlin from the ground-up
- Modular, feature-based architecture
- No rounded corners