diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 434056b20..ee34f01dd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -15,6 +15,7 @@ import android.widget.Toast; import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.preference.Preference; @@ -230,8 +231,11 @@ public class ContentSettingsFragment extends BasePreferenceFragment { }) .setPositiveButton(R.string.ok, (dialog, which) -> { dialog.dismiss(); - manager.loadSharedPreferences(PreferenceManager - .getDefaultSharedPreferences(requireContext())); + final Context context = requireContext(); + final SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + manager.loadSharedPreferences(prefs); + cleanImport(context, prefs); finishImport(importDataUri); }) .show(); @@ -243,6 +247,38 @@ public class ContentSettingsFragment extends BasePreferenceFragment { } } + /** + * Remove settings that are not supposed to be imported on different devices + * and reset them to default values. + * @param context the context used for the import + * @param prefs the preferences used while running the import + */ + private void cleanImport(@NonNull final Context context, + @NonNull final SharedPreferences prefs) { + // Check if media tunnelling needs to be disabled automatically, + // if it was disabled automatically in the imported preferences. + final String tunnelingKey = context.getString(R.string.disable_media_tunneling_key); + final String automaticTunnelingKey = + context.getString(R.string.disabled_media_tunneling_automatically_key); + // R.string.disable_media_tunneling_key should always be true + // if R.string.disabled_media_tunneling_automatically_key equals 1, + // but we double check here just to be sure and to avoid regressions + // caused by possible later modification of the media tunneling functionality. + // R.string.disabled_media_tunneling_automatically_key == 0: + // automatic value overridden by user in settings + // R.string.disabled_media_tunneling_automatically_key == -1: not set + final boolean wasMediaTunnelingDisabledAutomatically = + prefs.getInt(automaticTunnelingKey, -1) == 1 + && prefs.getBoolean(tunnelingKey, false); + if (wasMediaTunnelingDisabledAutomatically) { + prefs.edit() + .putInt(automaticTunnelingKey, -1) + .putBoolean(tunnelingKey, false) + .apply(); + NewPipeSettings.setMediaTunneling(context); + } + } + /** * Save import path and restart system. * diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt index 8adf6a649..92be93a29 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt @@ -67,6 +67,9 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { return ZipHelper.extractFileFromZip(file, fileLocator.settings.path, "newpipe.settings") } + /** + * Remove all shared preferences from the app and load the preferences supplied to the manager. + */ fun loadSharedPreferences(preferences: SharedPreferences) { try { val preferenceEditor = preferences.edit() diff --git a/app/src/main/java/org/schabi/newpipe/settings/ExoPlayerSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ExoPlayerSettingsFragment.java index 7e740f869..14dd0c409 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ExoPlayerSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ExoPlayerSettingsFragment.java @@ -1,8 +1,14 @@ package org.schabi.newpipe.settings; +import android.content.SharedPreferences; import android.os.Bundle; import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.SwitchPreferenceCompat; + +import org.schabi.newpipe.R; public class ExoPlayerSettingsFragment extends BasePreferenceFragment { @@ -10,5 +16,30 @@ public class ExoPlayerSettingsFragment extends BasePreferenceFragment { public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) { addPreferencesFromResourceRegistry(); + + final String disabledMediaTunnelingAutomaticallyKey = + getString(R.string.disabled_media_tunneling_automatically_key); + final SwitchPreferenceCompat disableMediaTunnelingPref = + (SwitchPreferenceCompat) requirePreference(R.string.disable_media_tunneling_key); + final SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(requireContext()); + final boolean mediaTunnelingAutomaticallyDisabled = + prefs.getInt(disabledMediaTunnelingAutomaticallyKey, -1) == 1; + final String summaryText = getString(R.string.disable_media_tunneling_summary); + disableMediaTunnelingPref.setSummary(mediaTunnelingAutomaticallyDisabled + ? summaryText + " " + getString(R.string.disable_media_tunneling_automatic_info) + : summaryText); + + disableMediaTunnelingPref.setOnPreferenceChangeListener((Preference p, Object enabled) -> { + if (Boolean.FALSE.equals(enabled)) { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putInt(disabledMediaTunnelingAutomaticallyKey, 0) + .apply(); + // the info text might have been shown before + p.setSummary(R.string.disable_media_tunneling_summary); + } + return true; + }); } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 16df646f9..2cca08072 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + import android.content.Context; import android.content.SharedPreferences; import android.os.Build; @@ -15,8 +17,6 @@ import org.schabi.newpipe.util.DeviceUtils; import java.io.File; import java.util.Set; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; - /* * Created by k3b on 07.01.2016. * @@ -61,7 +61,7 @@ public final class NewPipeSettings { } // first run migrations, then setDefaultValues, since the latter requires the correct types - SettingMigrations.initMigrations(context, isFirstRun); + SettingMigrations.runMigrationsIfNeeded(context, isFirstRun); // readAgain is true so that if new settings are added their default value is set PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); @@ -76,6 +76,8 @@ public final class NewPipeSettings { saveDefaultVideoDownloadDirectory(context); saveDefaultAudioDownloadDirectory(context); + + disableMediaTunnelingIfNecessary(context, isFirstRun); } static void saveDefaultVideoDownloadDirectory(final Context context) { @@ -152,4 +154,49 @@ public final class NewPipeSettings { return showSearchSuggestions(context, sharedPreferences, R.string.show_remote_search_suggestions_key); } + + private static void disableMediaTunnelingIfNecessary(@NonNull final Context context, + final boolean isFirstRun) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final String disabledTunnelingKey = context.getString(R.string.disable_media_tunneling_key); + final String disabledTunnelingAutomaticallyKey = + context.getString(R.string.disabled_media_tunneling_automatically_key); + final String blacklistVersionKey = + context.getString(R.string.media_tunneling_device_blacklist_version); + + final int lastMediaTunnelingUpdate = prefs.getInt(blacklistVersionKey, 0); + final boolean wasDeviceBlacklistUpdated = + DeviceUtils.MEDIA_TUNNELING_DEVICE_BLACKLIST_VERSION != lastMediaTunnelingUpdate; + final boolean wasMediaTunnelingEnabledByUser = + prefs.getInt(disabledTunnelingAutomaticallyKey, -1) == 0 + && !prefs.getBoolean(disabledTunnelingKey, false); + + if (Boolean.TRUE.equals(isFirstRun) + || (wasDeviceBlacklistUpdated && !wasMediaTunnelingEnabledByUser)) { + setMediaTunneling(context); + } + } + + /** + * Check if device does not support media tunneling + * and disable that exoplayer feature if necessary. + * @see DeviceUtils#shouldSupportMediaTunneling() + * @param context + */ + public static void setMediaTunneling(@NonNull final Context context) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + if (!DeviceUtils.shouldSupportMediaTunneling()) { + prefs.edit() + .putBoolean(context.getString(R.string.disable_media_tunneling_key), true) + .putInt(context.getString( + R.string.disabled_media_tunneling_automatically_key), 1) + .putInt(context.getString(R.string.media_tunneling_device_blacklist_version), + DeviceUtils.MEDIA_TUNNELING_DEVICE_BLACKLIST_VERSION) + .apply(); + } else { + prefs.edit() + .putInt(context.getString(R.string.media_tunneling_device_blacklist_version), + DeviceUtils.MEDIA_TUNNELING_DEVICE_BLACKLIST_VERSION).apply(); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 1bfaec6c2..215caaa38 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; @@ -30,9 +31,9 @@ public final class SettingMigrations { private static final String TAG = SettingMigrations.class.toString(); private static SharedPreferences sp; - public static final Migration MIGRATION_0_1 = new Migration(0, 1) { + private static final Migration MIGRATION_0_1 = new Migration(0, 1) { @Override - public void migrate(final Context context) { + public void migrate(@NonNull final Context context) { // We changed the content of the dialog which opens when sharing a link to NewPipe // by removing the "open detail page" option. // Therefore, show the dialog once again to ensure users need to choose again and are @@ -44,9 +45,9 @@ public final class SettingMigrations { } }; - public static final Migration MIGRATION_1_2 = new Migration(1, 2) { + private static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override - protected void migrate(final Context context) { + protected void migrate(@NonNull final Context context) { // The new application workflow introduced in #2907 allows minimizing videos // while playing to do other stuff within the app. // For an even better workflow, we minimize a stream when switching the app to play in @@ -63,9 +64,9 @@ public final class SettingMigrations { } }; - public static final Migration MIGRATION_2_3 = new Migration(2, 3) { + private static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override - protected void migrate(final Context context) { + protected void migrate(@NonNull final Context context) { // Storage Access Framework implementation was improved in #5415, allowing the modern // and standard way to access folders and files to be used consistently everywhere. // We reset the setting to its default value, i.e. "use SAF", since now there are no @@ -79,9 +80,9 @@ public final class SettingMigrations { } }; - public static final Migration MIGRATION_3_4 = new Migration(3, 4) { + private static final Migration MIGRATION_3_4 = new Migration(3, 4) { @Override - protected void migrate(final Context context) { + protected void migrate(@NonNull final Context context) { // Pull request #3546 added support for choosing the type of search suggestions to // show, replacing the on-off switch used before, so migrate the previous user choice @@ -108,9 +109,9 @@ public final class SettingMigrations { } }; - public static final Migration MIGRATION_4_5 = new Migration(4, 5) { + private static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override - protected void migrate(final Context context) { + protected void migrate(@NonNull final Context context) { final boolean brightness = sp.getBoolean("brightness_gesture_control", true); final boolean volume = sp.getBoolean("volume_gesture_control", true); @@ -144,10 +145,11 @@ public final class SettingMigrations { /** * Version number for preferences. Must be incremented every time a migration is necessary. */ - public static final int VERSION = 5; + private static final int VERSION = 5; - public static void initMigrations(final Context context, final boolean isFirstRun) { + public static void runMigrationsIfNeeded(@NonNull final Context context, + final boolean isFirstRun) { // setup migrations and check if there is something to do sp = PreferenceManager.getDefaultSharedPreferences(context); final String lastPrefVersionKey = context.getString(R.string.last_used_preferences_version); @@ -212,7 +214,7 @@ public final class SettingMigrations { return oldVersion >= currentVersion; } - protected abstract void migrate(Context context); + protected abstract void migrate(@NonNull Context context); } diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index f656c6144..e9678c2b0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -36,6 +36,91 @@ public final class DeviceUtils { private static Boolean isTV = null; private static Boolean isFireTV = null; + /** + *
The app version code that corresponds to the last update + * of the media tunneling device blacklist.
+ *The value of this variable needs to be updated everytime a new device that does not + * support media tunneling to match the upcoming version code.
+ * @see #shouldSupportMediaTunneling() + */ + public static final int MEDIA_TUNNELING_DEVICE_BLACKLIST_VERSION = 994; + + // region: devices not supporting media tunneling / media tunneling blacklist + /** + *Formuler Z8 Pro, Z8, CC, Z Alpha, Z+ Neo.
+ *Blacklist reason: black screen
+ *Board: HiSilicon Hi3798MV200
+ */ + private static final boolean HI3798MV200 = Build.VERSION.SDK_INT == 24 + && Build.DEVICE.equals("Hi3798MV200"); + /** + *Zephir TS43UHD-2.
+ *Blacklist reason: black screen
+ */ + private static final boolean CVT_MT5886_EU_1G = Build.VERSION.SDK_INT == 24 + && Build.DEVICE.equals("cvt_mt5886_eu_1g"); + /** + * Hilife TV. + *Blacklist reason: black screen
+ */ + private static final boolean REALTEKATV = Build.VERSION.SDK_INT == 25 + && Build.DEVICE.equals("RealtekATV"); + /** + *Phillips 4K (O)LED TV.
+ * Supports custom ROMs with different API levels + */ + private static final boolean PH7M_EU_5596 = Build.VERSION.SDK_INT >= 26 + && Build.DEVICE.equals("PH7M_EU_5596"); + /** + *Philips QM16XE.
+ *Blacklist reason: black screen
+ */ + private static final boolean QM16XE_U = Build.VERSION.SDK_INT == 23 + && Build.DEVICE.equals("QM16XE_U"); + /** + *Sony Bravia VH1.
+ *Processor: MT5895
+ *Blacklist reason: fullscreen crash / stuttering
+ */ + private static final boolean BRAVIA_VH1 = Build.VERSION.SDK_INT == 29 + && Build.DEVICE.equals("BRAVIA_VH1"); + /** + *Sony Bravia VH2.
+ *Blacklist reason: fullscreen crash; this includes model A90J as reported in + * + * #9023
+ */ + private static final boolean BRAVIA_VH2 = Build.VERSION.SDK_INT == 29 + && Build.DEVICE.equals("BRAVIA_VH2"); + /** + *Sony Bravia Android TV platform 2.
+ * Uses a MediaTek MT5891 (MT5596) SoC. + * @see + * https://github.com/CiNcH83/bravia_atv2 + */ + private static final boolean BRAVIA_ATV2 = Build.DEVICE.equals("BRAVIA_ATV2"); + /** + *Sony Bravia Android TV platform 3 4K.
+ *Uses ARM MT5891 and a {@link #BRAVIA_ATV2} motherboard.
+ * + * @see + * https://browser.geekbench.com/v4/cpu/9101105 + */ + private static final boolean BRAVIA_ATV3_4K = Build.DEVICE.equals("BRAVIA_ATV3_4K"); + /** + *Panasonic 4KTV-JUP.
+ *Blacklist reason: fullscreen crash
+ */ + private static final boolean TX_50JXW834 = Build.DEVICE.equals("TX_50JXW834"); + /** + *Bouygtel4K / Bouygues Telecom Bbox 4K.
+ *Blacklist reason: black screen; reported at + * + * #10122
+ */ + private static final boolean HMB9213NW = Build.DEVICE.equals("HMB9213NW"); + // endregion + private DeviceUtils() { } @@ -224,4 +309,30 @@ public final class DeviceUtils { return point.y; } } + + /** + *Some devices have broken tunneled video playback but claim to support it.
+ *This can cause a black video player surface while attempting to play a video or + * crashes while entering or exiting the full screen player. + * The issue effects Android TVs most commonly. + * See #5911 and + * #9023 for more info.
+ * @Note Update {@link #MEDIA_TUNNELING_DEVICE_BLACKLIST_VERSION} + * when adding a new device to the method. + * @return {@code false} if affected device; {@code true} otherwise + */ + public static boolean shouldSupportMediaTunneling() { + // Maintainers note: update MEDIA_TUNNELING_DEVICES_UPDATE_APP_VERSION_CODE + return !HI3798MV200 + && !CVT_MT5886_EU_1G + && !REALTEKATV + && !QM16XE_U + && !BRAVIA_VH1 + && !BRAVIA_VH2 + && !BRAVIA_ATV2 + && !BRAVIA_ATV3_4K + && !PH7M_EU_5596 + && !TX_50JXW834 + && !HMB9213NW; + } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index eebe29d81..a41d41fdb 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1382,6 +1382,8 @@