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:
parent
792cc22583
commit
b9658c698b
4 changed files with 71 additions and 52 deletions
|
@ -20,8 +20,10 @@ package org.oxycblt.auxio.playback.queue
|
||||||
|
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
@ -32,15 +34,15 @@ import kotlin.math.sign
|
||||||
/**
|
/**
|
||||||
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
||||||
* and [QueueAdapter] simultaneously.
|
* and [QueueAdapter] simultaneously.
|
||||||
* @param onItemScroll Callback for when an item begins to scroll off-screen. Argument passed
|
|
||||||
* is the dY value.
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class QueueDragCallback(
|
class QueueDragCallback(
|
||||||
private val playbackModel: PlaybackViewModel,
|
private val playbackModel: PlaybackViewModel,
|
||||||
private val onItemScroll: (Int) -> Unit
|
private val coordinator: CoordinatorLayout,
|
||||||
|
private val appBar: AppBarLayout
|
||||||
) : ItemTouchHelper.Callback() {
|
) : ItemTouchHelper.Callback() {
|
||||||
private lateinit var queueAdapter: QueueAdapter
|
private lateinit var queueAdapter: QueueAdapter
|
||||||
|
private val tConsumed = IntArray(2)
|
||||||
private var shouldLift = true
|
private var shouldLift = true
|
||||||
|
|
||||||
override fun getMovementFlags(
|
override fun getMovementFlags(
|
||||||
|
@ -79,24 +81,18 @@ class QueueDragCallback(
|
||||||
val result = clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
|
val result = clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
|
||||||
|
|
||||||
recyclerView.post {
|
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
|
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(
|
override fun onChildDraw(
|
||||||
c: Canvas,
|
c: Canvas,
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
|
@ -107,10 +103,12 @@ class QueueDragCallback(
|
||||||
isCurrentlyActive: Boolean
|
isCurrentlyActive: Boolean
|
||||||
) {
|
) {
|
||||||
// The material design page on elevation has a cool example of draggable items elevating
|
// 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
|
// themselves when being dragged. Too bad google's implementation of this doesn't even
|
||||||
// this in your own app :^). To emulate it, I check if this child is in a drag state and
|
// 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
|
// then animate an elevation change. This animation also changes the background so that
|
||||||
// the item will actually draw over.
|
// 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
|
val view = viewHolder.itemView
|
||||||
|
|
||||||
|
@ -129,7 +127,7 @@ class QueueDragCallback(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
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.
|
// and set the background to null again so a seam doesn't show up in further actions.
|
||||||
|
|
||||||
val view = viewHolder.itemView
|
val view = viewHolder.itemView
|
||||||
|
@ -148,6 +146,18 @@ class QueueDragCallback(
|
||||||
super.clearView(recyclerView, viewHolder)
|
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) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter)
|
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
@ -53,16 +52,11 @@ class QueueFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentQueueBinding.inflate(inflater)
|
val binding = FragmentQueueBinding.inflate(inflater)
|
||||||
|
|
||||||
val callback = QueueDragCallback(playbackModel) { dY ->
|
val callback = QueueDragCallback(
|
||||||
// By default, CoordinatorLayout is not aware of scroll events originating from
|
playbackModel,
|
||||||
// when an item is scrolled off-screen, so we manually add a scroll event ourselves.
|
binding.queueCoordinator,
|
||||||
(binding.queueAppbar.layoutParams as CoordinatorLayout.LayoutParams).behavior
|
binding.queueAppbar
|
||||||
?.onNestedPreScroll(
|
)
|
||||||
binding.queueCoordinator, binding.queueAppbar,
|
|
||||||
binding.queueRecycler, 0, dY,
|
|
||||||
IntArray(2), 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val helper = ItemTouchHelper(callback)
|
val helper = ItemTouchHelper(callback)
|
||||||
val queueAdapter = QueueAdapter(helper, playbackModel)
|
val queueAdapter = QueueAdapter(helper, playbackModel)
|
||||||
|
@ -110,7 +104,6 @@ class QueueFragment : Fragment() {
|
||||||
if (isShuffling != lastShuffle) {
|
if (isShuffling != lastShuffle) {
|
||||||
lastShuffle = isShuffling
|
lastShuffle = isShuffling
|
||||||
|
|
||||||
binding.queueRecycler.scrollBy(0, 100)
|
|
||||||
binding.queueRecycler.scrollToPosition(0)
|
binding.queueRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,8 +113,8 @@ class QueueFragment : Fragment() {
|
||||||
|
|
||||||
private fun setupEdgeForQueue(binding: FragmentQueueBinding) {
|
private fun setupEdgeForQueue(binding: FragmentQueueBinding) {
|
||||||
if (isEdgeOn()) {
|
if (isEdgeOn()) {
|
||||||
// Account for the side navigation bar if required.
|
|
||||||
binding.root.setOnApplyWindowInsetsListener { v, insets ->
|
binding.root.setOnApplyWindowInsetsListener { v, insets ->
|
||||||
|
// Account for the side navigation bar if required.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val bars = insets.getInsets(WindowInsets.Type.systemBars())
|
val bars = insets.getInsets(WindowInsets.Type.systemBars())
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.oxycblt.auxio.settings
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -74,27 +75,43 @@ class AboutFragment : Fragment() {
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
)
|
)
|
||||||
|
|
||||||
val pkgName = requireContext().packageManager.resolveActivity(
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
browserIntent, PackageManager.MATCH_DEFAULT_ONLY
|
// Android 11 seems to now handle the app chooser situations on its own now
|
||||||
)?.activityInfo?.packageName
|
// [along with adding a new permission that breaks the old manual code], so
|
||||||
|
// we just do a typical activity launch.
|
||||||
if (pkgName != null) {
|
try {
|
||||||
if (pkgName == "android") {
|
requireContext().startActivity(browserIntent)
|
||||||
// No default browser [Must open app chooser, may not be supported]
|
} catch (e: ActivityNotFoundException) {
|
||||||
openAppChooser(browserIntent)
|
// No app installed to open the link
|
||||||
} else {
|
requireContext().showToast(R.string.err_no_app)
|
||||||
try {
|
|
||||||
browserIntent.setPackage(pkgName)
|
|
||||||
startActivity(browserIntent)
|
|
||||||
} catch (exception: ActivityNotFoundException) {
|
|
||||||
// Not browser but an app chooser due to OEM garbage
|
|
||||||
browserIntent.setPackage(null)
|
|
||||||
openAppChooser(browserIntent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No app installed to open the link
|
// On older versions of android, opening links from an ACTION_VIEW intent might
|
||||||
requireContext().showToast(R.string.err_no_app)
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,5 +13,4 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele
|
||||||
- Audio Focus / Headset Management
|
- Audio Focus / Headset Management
|
||||||
- No internet connectivity whatsoever
|
- No internet connectivity whatsoever
|
||||||
- Kotlin from the ground-up
|
- Kotlin from the ground-up
|
||||||
- Modular, feature-based architecture
|
|
||||||
- No rounded corners
|
- No rounded corners
|
||||||
|
|
Loading…
Reference in a new issue