diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 39bdb7fb193..0aff9d6ec2b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -99,6 +99,7 @@ import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.CustomRenderersFactory; +import org.schabi.newpipe.player.helper.DisplayAwareContextWrapper; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -581,7 +582,12 @@ private void initUIsForCurrentPlayerType() { if (playerType == PlayerType.AUDIO) { return null; } else { - return PlayerBinding.inflate(LayoutInflater.from(context)); + final DisplayAwareContextWrapper viewContext = + new DisplayAwareContextWrapper(context); + final PlayerBinding newBinding = PlayerBinding.inflate( + LayoutInflater.from(context).cloneInContext(viewContext)); + viewContext.setAnchor(newBinding.getRoot()); + return newBinding; } }); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/DisplayAwareContextWrapper.kt b/app/src/main/java/org/schabi/newpipe/player/helper/DisplayAwareContextWrapper.kt new file mode 100644 index 00000000000..ef51ac4605a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/DisplayAwareContextWrapper.kt @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.player.helper + +import android.content.Context +import android.content.ContextWrapper +import android.view.View + +/** + * Context for the player views, which are inflated with the player service context. That context + * is bound to the default display, so windows added on behalf of a view (e.g. popup menus and + * hover tooltips) would be added to the wrong display when the player is shown on a secondary + * display, like Samsung DeX, causing a [android.view.WindowManager.BadTokenException]. This + * wrapper returns the [android.view.WindowManager] of the display the anchor view is on. + */ +class DisplayAwareContextWrapper(base: Context) : ContextWrapper(base) { + var anchor: View? = null + + override fun getSystemService(name: String): Any? { + if (name == WINDOW_SERVICE) { + anchor?.display?.let { display -> + return baseContext.createDisplayContext(display).getSystemService(name) + } + } + return super.getSystemService(name) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 65af3c44ae7..ee20d8a914d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -16,6 +16,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; +import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -186,12 +187,15 @@ private void initViews() { binding.playbackSeekBar.getProgressDrawable() .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); - final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, + // use the context of the views, so that the popup menus are shown on the display + // the player is on (see DisplayAwareContextWrapper) + final Context viewContext = binding.getRoot().getContext(); + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(viewContext, R.style.DarkPopupMenu); qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); audioTrackPopupMenu = new PopupMenu(themeWrapper, binding.audioTrackTextView); - playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); + playbackSpeedPopupMenu = new PopupMenu(viewContext, binding.playbackSpeed); captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); binding.progressBarLoadingPanel.getIndeterminateDrawable()