From 780e6a4848813108eb4d6b28acb18bb48001f4a3 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 06:39:07 +0200 Subject: [PATCH 1/9] Convert newpipe/util/PlayButtonHelper to kotlin --- .../schabi/newpipe/util/PlayButtonHelper.java | 94 ------------------ .../schabi/newpipe/util/PlayButtonHelper.kt | 96 +++++++++++++++++++ 2 files changed, 96 insertions(+), 94 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java deleted file mode 100644 index 655ea7092..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.schabi.newpipe.util; - -import android.content.Context; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.preference.PreferenceManager; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.databinding.PlaylistControlBinding; -import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; -import org.schabi.newpipe.player.PlayerType; - -/** - * Utility class for play buttons and their respective click listeners. - */ -public final class PlayButtonHelper { - - private PlayButtonHelper() { - // utility class - } - - /** - * Initialize {@link android.view.View.OnClickListener OnClickListener} - * and {@link android.view.View.OnLongClickListener OnLongClickListener} for playlist control - * buttons defined in {@link R.layout#playlist_control}. - * - * @param activity The activity to use for the {@link android.widget.Toast Toast}. - * @param playlistControlBinding The binding of the - * {@link R.layout#playlist_control playlist control layout}. - * @param fragment The fragment to get the play queue from. - */ - public static void initPlaylistControlClickListener( - @NonNull final AppCompatActivity activity, - @NonNull final PlaylistControlBinding playlistControlBinding, - @NonNull final PlaylistControlViewHolder fragment) { - // click listener - playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view -> { - NavigationHelper.playOnMainPlayer(activity, fragment.getPlayQueue()); - showHoldToAppendToastIfNeeded(activity); - }); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view -> { - NavigationHelper.playOnPopupPlayer(activity, fragment.getPlayQueue(), false); - showHoldToAppendToastIfNeeded(activity); - }); - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view -> { - NavigationHelper.playOnBackgroundPlayer(activity, fragment.getPlayQueue(), false); - showHoldToAppendToastIfNeeded(activity); - }); - - // long click listener - playlistControlBinding.playlistCtrlPlayAllButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.MAIN); - return true; - }); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.POPUP); - return true; - }); - playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.AUDIO); - return true; - }); - } - - /** - * Show the "hold to append" toast if the corresponding preference is enabled. - * - * @param context The context to show the toast. - */ - private static void showHoldToAppendToastIfNeeded(@NonNull final Context context) { - if (shouldShowHoldToAppendTip(context)) { - Toast.makeText(context, R.string.hold_to_append, Toast.LENGTH_SHORT).show(); - } - - } - - /** - * Check if the "hold to append" toast should be shown. - * - *

- * The tip is shown if the corresponding preference is enabled. - * This is the default behaviour. - *

- * - * @param context The context to get the preference. - * @return {@code true} if the tip should be shown, {@code false} otherwise. - */ - public static boolean shouldShowHoldToAppendTip(@NonNull final Context context) { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.show_hold_to_append_key), true); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.kt b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.kt new file mode 100644 index 000000000..40734ec58 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.kt @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import android.content.Context +import android.view.View +import android.view.View.OnLongClickListener +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.PreferenceManager +import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.PlaylistControlBinding +import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder +import org.schabi.newpipe.player.PlayerType + +/** + * Utility class for play buttons and their respective click listeners. + */ +object PlayButtonHelper { + /** + * Initialize [OnClickListener][View.OnClickListener] + * and [OnLongClickListener][OnLongClickListener] for playlist control + * buttons defined in [R.layout.playlist_control]. + * + * @param activity The activity to use for the [Toast][Toast]. + * @param playlistControlBinding The binding of the + * [playlist control layout][R.layout.playlist_control]. + * @param fragment The fragment to get the play queue from. + */ + @JvmStatic + fun initPlaylistControlClickListener( + activity: AppCompatActivity, + playlistControlBinding: PlaylistControlBinding, + fragment: PlaylistControlViewHolder + ) { + // click listener + playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener { + NavigationHelper.playOnMainPlayer(activity, fragment.getPlayQueue()) + showHoldToAppendToastIfNeeded(activity) + } + playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener { + NavigationHelper.playOnPopupPlayer(activity, fragment.getPlayQueue(), false) + showHoldToAppendToastIfNeeded(activity) + } + playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener { + NavigationHelper.playOnBackgroundPlayer(activity, fragment.getPlayQueue(), false) + showHoldToAppendToastIfNeeded(activity) + } + + // long click listener + playlistControlBinding.playlistCtrlPlayAllButton.setOnLongClickListener { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.MAIN) + true + } + playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.POPUP) + true + } + playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.AUDIO) + true + } + } + + /** + * Show the "hold to append" toast if the corresponding preference is enabled. + * + * @param context The context to show the toast. + */ + private fun showHoldToAppendToastIfNeeded(context: Context) { + if (shouldShowHoldToAppendTip(context)) { + Toast.makeText(context, R.string.hold_to_append, Toast.LENGTH_SHORT).show() + } + } + + /** + * Check if the "hold to append" toast should be shown. + * + * + * + * The tip is shown if the corresponding preference is enabled. + * This is the default behaviour. + * + * + * @param context The context to get the preference. + * @return `true` if the tip should be shown, `false` otherwise. + */ + @JvmStatic + fun shouldShowHoldToAppendTip(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.show_hold_to_append_key), true) + } +} From 48467669b6d73fced59cdd93cced3a7da7f8ddc2 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 07:00:26 +0200 Subject: [PATCH 2/9] Convert newpipe/util/PeertubeHelper to kotlin --- .../schabi/newpipe/util/PeertubeHelper.java | 69 ------------------- .../org/schabi/newpipe/util/PeertubeHelper.kt | 53 ++++++++++++++ 2 files changed, 53 insertions(+), 69 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java deleted file mode 100644 index 34f99d262..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.schabi.newpipe.util; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -import com.grack.nanojson.JsonArray; -import com.grack.nanojson.JsonObject; -import com.grack.nanojson.JsonParser; -import com.grack.nanojson.JsonParserException; -import com.grack.nanojson.JsonStringWriter; -import com.grack.nanojson.JsonWriter; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.ServiceList; -import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; - -import java.util.ArrayList; -import java.util.List; - -public final class PeertubeHelper { - private PeertubeHelper() { } - - public static List getInstanceList(final Context context) { - final SharedPreferences sharedPreferences = PreferenceManager - .getDefaultSharedPreferences(context); - final String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); - final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); - if (null == savedJson) { - return List.of(getCurrentInstance()); - } - - try { - final JsonArray array = JsonParser.object().from(savedJson).getArray("instances"); - final List result = new ArrayList<>(); - for (final Object o : array) { - if (o instanceof JsonObject) { - final JsonObject instance = (JsonObject) o; - final String name = instance.getString("name"); - final String url = instance.getString("url"); - result.add(new PeertubeInstance(url, name)); - } - } - return result; - } catch (final JsonParserException e) { - return List.of(getCurrentInstance()); - } - } - - public static PeertubeInstance selectInstance(final PeertubeInstance instance, - final Context context) { - final SharedPreferences sharedPreferences = PreferenceManager - .getDefaultSharedPreferences(context); - final String selectedInstanceKey = - context.getString(R.string.peertube_selected_instance_key); - final JsonStringWriter jsonWriter = JsonWriter.string().object(); - jsonWriter.value("name", instance.getName()); - jsonWriter.value("url", instance.getUrl()); - final String jsonToSave = jsonWriter.end().done(); - sharedPreferences.edit().putString(selectedInstanceKey, jsonToSave).apply(); - ServiceList.PeerTube.setInstance(instance); - return instance; - } - - public static PeertubeInstance getCurrentInstance() { - return ServiceList.PeerTube.getInstance(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt new file mode 100644 index 000000000..3da7958d2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2019-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import android.content.Context +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import com.grack.nanojson.JsonObject +import com.grack.nanojson.JsonParser +import com.grack.nanojson.JsonWriter +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.ServiceList +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance + +object PeertubeHelper { + @JvmStatic + fun getInstanceList(context: Context): List { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val savedInstanceListKey = context.getString(R.string.peertube_instance_list_key) + val savedJson = sharedPreferences.getString(savedInstanceListKey, null) + if (savedJson == null) { + return listOf(currentInstance) + } + + return runCatching { + JsonParser.`object`().from(savedJson).getArray("instances") + .filterIsInstance() + .map { PeertubeInstance(it.getString("url"), it.getString("name")) } + }.getOrDefault(listOf(currentInstance)) + } + + @JvmStatic + fun selectInstance(instance: PeertubeInstance, context: Context): PeertubeInstance { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val selectedInstanceKey = context.getString(R.string.peertube_selected_instance_key) + + val jsonWriter = JsonWriter.string().`object`() + jsonWriter.value("name", instance.name) + jsonWriter.value("url", instance.url) + val jsonToSave = jsonWriter.end().done() + + sharedPreferences.edit { putString(selectedInstanceKey, jsonToSave) } + ServiceList.PeerTube.instance = instance + return instance + } + + @JvmStatic + val currentInstance: PeertubeInstance + get() = ServiceList.PeerTube.instance +} From 6cf932b2a714a1011849b3b2eb2fce8d0808f4aa Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 11:51:41 +0200 Subject: [PATCH 3/9] Convert newpipe/ExitActivity to kotlin --- .../java/org/schabi/newpipe/ExitActivity.java | 50 ------------------- .../java/org/schabi/newpipe/ExitActivity.kt | 38 ++++++++++++++ 2 files changed, 38 insertions(+), 50 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ExitActivity.java create mode 100644 app/src/main/java/org/schabi/newpipe/ExitActivity.kt diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.java b/app/src/main/java/org/schabi/newpipe/ExitActivity.java deleted file mode 100644 index bd1351f0c..000000000 --- a/app/src/main/java/org/schabi/newpipe/ExitActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.schabi.newpipe; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; - -import org.schabi.newpipe.util.NavigationHelper; - -/* - * Copyright (C) Hans-Christoph Steiner 2016 - * ExitActivity.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class ExitActivity extends Activity { - - public static void exitAndRemoveFromRecentApps(final Activity activity) { - final Intent intent = new Intent(activity, ExitActivity.class); - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_NO_ANIMATION); - - activity.startActivity(intent); - } - - @SuppressLint("NewApi") - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - finishAndRemoveTask(); - - NavigationHelper.restartApp(this); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.kt b/app/src/main/java/org/schabi/newpipe/ExitActivity.kt new file mode 100644 index 000000000..854a07c43 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2016-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import org.schabi.newpipe.util.NavigationHelper +class ExitActivity : Activity() { + @SuppressLint("NewApi") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + finishAndRemoveTask() + + NavigationHelper.restartApp(this) + } + + companion object { + @JvmStatic + fun exitAndRemoveFromRecentApps(activity: Activity) { + val intent = Intent(activity, ExitActivity::class.java) + + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + or Intent.FLAG_ACTIVITY_CLEAR_TASK + or Intent.FLAG_ACTIVITY_NO_ANIMATION + ) + + activity.startActivity(intent) + } + } +} From d665a4f01688e09eecacfc815bd58f2c57bd09f4 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 12:11:48 +0200 Subject: [PATCH 4/9] Convert newpipe/util/NewPipeTextViewHelper to kotlin --- .../newpipe/util/NewPipeTextViewHelper.java | 61 ------------------- .../newpipe/util/NewPipeTextViewHelper.kt | 60 ++++++++++++++++++ 2 files changed, 60 insertions(+), 61 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.java b/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.java deleted file mode 100644 index cf1a9a03a..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.schabi.newpipe.util; - -import android.content.Context; -import android.text.Selection; -import android.text.Spannable; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.views.NewPipeEditText; -import org.schabi.newpipe.views.NewPipeTextView; - -public final class NewPipeTextViewHelper { - private NewPipeTextViewHelper() { - } - - /** - * Share the selected text of {@link NewPipeTextView NewPipeTextViews} and - * {@link NewPipeEditText NewPipeEditTexts} with - * {@link ShareUtils#shareText(Context, String, String)}. - * - *

- * This allows EMUI users to get the Android share sheet instead of the EMUI share sheet when - * using the {@code Share} command of the popup menu which appears when selecting text. - *

- * - * @param textView the {@link TextView} on which sharing the selected text. It should be a - * {@link NewPipeTextView} or a {@link NewPipeEditText} (even if - * {@link TextView standard TextViews} are supported). - */ - public static void shareSelectedTextWithShareUtils(@NonNull final TextView textView) { - final CharSequence textViewText = textView.getText(); - shareSelectedTextIfNotNullAndNotEmpty(textView, getSelectedText(textView, textViewText)); - if (textViewText instanceof Spannable) { - Selection.setSelection((Spannable) textViewText, textView.getSelectionEnd()); - } - } - - @Nullable - private static CharSequence getSelectedText(@NonNull final TextView textView, - @Nullable final CharSequence text) { - if (!textView.hasSelection() || text == null) { - return null; - } - - final int start = textView.getSelectionStart(); - final int end = textView.getSelectionEnd(); - return String.valueOf(start > end ? text.subSequence(end, start) - : text.subSequence(start, end)); - } - - private static void shareSelectedTextIfNotNullAndNotEmpty( - @NonNull final TextView textView, - @Nullable final CharSequence selectedText) { - if (selectedText != null && selectedText.length() != 0) { - ShareUtils.shareText(textView.getContext(), "", selectedText.toString()); - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt b/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt new file mode 100644 index 000000000..33046c967 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2021-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import android.text.Selection +import android.text.Spannable +import android.widget.TextView +import org.schabi.newpipe.util.external_communication.ShareUtils + +object NewPipeTextViewHelper { + /** + * Share the selected text of [NewPipeTextViews][org.schabi.newpipe.views.NewPipeTextView] and + * [NewPipeEditTexts][org.schabi.newpipe.views.NewPipeEditText] with + * [ShareUtils.shareText]. + * + * + * + * This allows EMUI users to get the Android share sheet instead of the EMUI share sheet when + * using the `Share` command of the popup menu which appears when selecting text. + * + * + * @param textView the [TextView] on which sharing the selected text. It should be a + * [org.schabi.newpipe.views.NewPipeTextView] or a [org.schabi.newpipe.views.NewPipeEditText] + * (even if [standard TextViews][TextView] are supported). + */ + @JvmStatic + fun shareSelectedTextWithShareUtils(textView: TextView) { + val textViewText = textView.getText() + shareSelectedTextIfNotNullAndNotEmpty(textView, getSelectedText(textView, textViewText)) + if (textViewText is Spannable) { + Selection.setSelection(textViewText, textView.selectionEnd) + } + } + + private fun getSelectedText(textView: TextView, text: CharSequence?): CharSequence? { + if (!textView.hasSelection() || text == null) { + return null + } + + val start = textView.selectionStart + val end = textView.selectionEnd + return if (start > end) { + text.subSequence(end, start) + } else { + text.subSequence(start, end) + } + } + + private fun shareSelectedTextIfNotNullAndNotEmpty( + textView: TextView, + selectedText: CharSequence? + ) { + if (!selectedText.isNullOrEmpty()) { + ShareUtils.shareText(textView.getContext(), "", selectedText.toString()) + } + } +} From 09a746dd6a131bee665ed44e7cff1d554b85e9bb Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 12:45:56 +0200 Subject: [PATCH 5/9] Convert newpipe/util/ServiceHelper to kotlin --- .../schabi/newpipe/util/ServiceHelper.java | 213 ------------------ .../org/schabi/newpipe/util/ServiceHelper.kt | 173 ++++++++++++++ 2 files changed, 173 insertions(+), 213 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java deleted file mode 100644 index c712157b3..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.schabi.newpipe.util; - -import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.preference.PreferenceManager; - -import com.grack.nanojson.JsonObject; -import com.grack.nanojson.JsonParser; -import com.grack.nanojson.JsonParserException; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.ServiceList; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; - -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -public final class ServiceHelper { - private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; - - private ServiceHelper() { } - - @DrawableRes - public static int getIcon(final int serviceId) { - switch (serviceId) { - case 0: - return R.drawable.ic_smart_display; - case 1: - return R.drawable.ic_cloud; - case 2: - return R.drawable.ic_placeholder_media_ccc; - case 3: - return R.drawable.ic_placeholder_peertube; - case 4: - return R.drawable.ic_placeholder_bandcamp; - default: - return R.drawable.ic_circle; - } - } - - public static String getTranslatedFilterString(final String filter, final Context c) { - switch (filter) { - case "all": - return c.getString(R.string.all); - case "videos": - case "sepia_videos": - case "music_videos": - return c.getString(R.string.videos_string); - case "channels": - return c.getString(R.string.channels); - case "playlists": - case "music_playlists": - return c.getString(R.string.playlists); - case "tracks": - return c.getString(R.string.tracks); - case "users": - return c.getString(R.string.users); - case "conferences": - return c.getString(R.string.conferences); - case "events": - return c.getString(R.string.events); - case "music_songs": - return c.getString(R.string.songs); - case "music_albums": - return c.getString(R.string.albums); - case "music_artists": - return c.getString(R.string.artists); - default: - return filter; - } - } - - /** - * Get a resource string with instructions for importing subscriptions for each service. - * - * @param serviceId service to get the instructions for - * @return the string resource containing the instructions or -1 if the service don't support it - */ - @StringRes - public static int getImportInstructions(final int serviceId) { - switch (serviceId) { - case 0: - return R.string.import_youtube_instructions; - case 1: - return R.string.import_soundcloud_instructions; - default: - return -1; - } - } - - /** - * For services that support importing from a channel url, return a hint that will - * be used in the EditText that the user will type in his channel url. - * - * @param serviceId service to get the hint for - * @return the hint's string resource or -1 if the service don't support it - */ - @StringRes - public static int getImportInstructionsHint(final int serviceId) { - switch (serviceId) { - case 1: - return R.string.import_soundcloud_instructions_hint; - default: - return -1; - } - } - - public static int getSelectedServiceId(final Context context) { - return Optional.ofNullable(getSelectedService(context)) - .orElse(DEFAULT_FALLBACK_SERVICE) - .getServiceId(); - } - - @Nullable - public static StreamingService getSelectedService(final Context context) { - final String serviceName = PreferenceManager.getDefaultSharedPreferences(context) - .getString(context.getString(R.string.current_service_key), - context.getString(R.string.default_service_value)); - - try { - return NewPipe.getService(serviceName); - } catch (final ExtractionException e) { - return null; - } - } - - @NonNull - public static String getNameOfServiceById(final int serviceId) { - return ServiceList.all().stream() - .filter(s -> s.getServiceId() == serviceId) - .findFirst() - .map(StreamingService::getServiceInfo) - .map(StreamingService.ServiceInfo::getName) - .orElse(""); - } - - /** - * @param serviceId the id of the service - * @return the service corresponding to the provided id - * @throws java.util.NoSuchElementException if there is no service with the provided id - */ - @NonNull - public static StreamingService getServiceById(final int serviceId) { - return ServiceList.all().stream() - .filter(s -> s.getServiceId() == serviceId) - .findFirst() - .orElseThrow(); - } - - public static void setSelectedServiceId(final Context context, final int serviceId) { - String serviceName; - try { - serviceName = NewPipe.getService(serviceId).getServiceInfo().getName(); - } catch (final ExtractionException e) { - serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName(); - } - - setSelectedServicePreferences(context, serviceName); - } - - private static void setSelectedServicePreferences(final Context context, - final String serviceName) { - PreferenceManager.getDefaultSharedPreferences(context).edit(). - putString(context.getString(R.string.current_service_key), serviceName).apply(); - } - - public static long getCacheExpirationMillis(final int serviceId) { - if (serviceId == SoundCloud.getServiceId()) { - return TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES); - } else { - return TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); - } - } - - public static void initService(final Context context, final int serviceId) { - if (serviceId == ServiceList.PeerTube.getServiceId()) { - final SharedPreferences sharedPreferences = PreferenceManager - .getDefaultSharedPreferences(context); - final String json = sharedPreferences.getString(context.getString( - R.string.peertube_selected_instance_key), null); - if (null == json) { - return; - } - - final JsonObject jsonObject; - try { - jsonObject = JsonParser.object().from(json); - } catch (final JsonParserException e) { - return; - } - final String name = jsonObject.getString("name"); - final String url = jsonObject.getString("url"); - final PeertubeInstance instance = new PeertubeInstance(url, name); - ServiceList.PeerTube.setInstance(instance); - } - } - - public static void initServices(final Context context) { - for (final StreamingService s : ServiceList.all()) { - initService(context, s.getServiceId()); - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt new file mode 100644 index 000000000..99cae0010 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt @@ -0,0 +1,173 @@ +/* + * SPDX-FileCopyrightText: 2018-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import com.grack.nanojson.JsonObject +import com.grack.nanojson.JsonParser +import com.grack.nanojson.JsonParserException +import java.util.concurrent.TimeUnit +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.NewPipe +import org.schabi.newpipe.extractor.ServiceList +import org.schabi.newpipe.extractor.StreamingService +import org.schabi.newpipe.extractor.exceptions.ExtractionException +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance +import org.schabi.newpipe.ktx.getStringSafe + +object ServiceHelper { + private val DEFAULT_FALLBACK_SERVICE: StreamingService = ServiceList.YouTube + + @JvmStatic + @DrawableRes + fun getIcon(serviceId: Int): Int { + return when (serviceId) { + 0 -> R.drawable.ic_smart_display + 1 -> R.drawable.ic_cloud + 2 -> R.drawable.ic_placeholder_media_ccc + 3 -> R.drawable.ic_placeholder_peertube + 4 -> R.drawable.ic_placeholder_bandcamp + else -> R.drawable.ic_circle + } + } + + @JvmStatic + fun getTranslatedFilterString(filter: String, context: Context): String { + return when (filter) { + "all" -> context.getString(R.string.all) + "videos", "sepia_videos", "music_videos" -> context.getString(R.string.videos_string) + "channels" -> context.getString(R.string.channels) + "playlists", "music_playlists" -> context.getString(R.string.playlists) + "tracks" -> context.getString(R.string.tracks) + "users" -> context.getString(R.string.users) + "conferences" -> context.getString(R.string.conferences) + "events" -> context.getString(R.string.events) + "music_songs" -> context.getString(R.string.songs) + "music_albums" -> context.getString(R.string.albums) + "music_artists" -> context.getString(R.string.artists) + else -> filter + } + } + + /** + * Get a resource string with instructions for importing subscriptions for each service. + * + * @param serviceId service to get the instructions for + * @return the string resource containing the instructions or -1 if the service don't support it + */ + @JvmStatic + @StringRes + fun getImportInstructions(serviceId: Int): Int { + return when (serviceId) { + 0 -> R.string.import_youtube_instructions + 1 -> R.string.import_soundcloud_instructions + else -> -1 + } + } + + /** + * For services that support importing from a channel url, return a hint that will + * be used in the EditText that the user will type in his channel url. + * + * @param serviceId service to get the hint for + * @return the hint's string resource or -1 if the service don't support it + */ + @JvmStatic + @StringRes + fun getImportInstructionsHint(serviceId: Int): Int { + return when (serviceId) { + 1 -> R.string.import_soundcloud_instructions_hint + else -> -1 + } + } + + @JvmStatic + fun getSelectedServiceId(context: Context): Int { + return (getSelectedService(context) ?: DEFAULT_FALLBACK_SERVICE).serviceId + } + + @JvmStatic + fun getSelectedService(context: Context): StreamingService? { + val serviceName: String = PreferenceManager.getDefaultSharedPreferences(context) + .getStringSafe( + context.getString(R.string.current_service_key), + context.getString(R.string.default_service_value) + ) + + return runCatching { NewPipe.getService(serviceName) }.getOrNull() + } + + @JvmStatic + fun getNameOfServiceById(serviceId: Int): String { + return ServiceList.all().stream() + .filter { it.serviceId == serviceId } + .findFirst() + .map(StreamingService::getServiceInfo) + .map(StreamingService.ServiceInfo::getName) + .orElse("") + } + + /** + * @param serviceId the id of the service + * @return the service corresponding to the provided id + * @throws java.util.NoSuchElementException if there is no service with the provided id + */ + @JvmStatic + fun getServiceById(serviceId: Int): StreamingService { + return ServiceList.all().firstNotNullOf { it.takeIf { it.serviceId == serviceId } } + } + + @JvmStatic + fun setSelectedServiceId(context: Context, serviceId: Int) { + val serviceName = runCatching { NewPipe.getService(serviceId).serviceInfo.name } + .getOrDefault(DEFAULT_FALLBACK_SERVICE.serviceInfo.name) + + setSelectedServicePreferences(context, serviceName) + } + + private fun setSelectedServicePreferences(context: Context, serviceName: String?) { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + sp.edit { putString(context.getString(R.string.current_service_key), serviceName) } + } + + @JvmStatic + fun getCacheExpirationMillis(serviceId: Int): Long { + return if (serviceId == ServiceList.SoundCloud.serviceId) { + TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES) + } else { + TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS) + } + } + + fun initService(context: Context, serviceId: Int) { + if (serviceId == ServiceList.PeerTube.serviceId) { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val json = sharedPreferences.getString( + context.getString(R.string.peertube_selected_instance_key), + null + ) + if (null == json) { + return + } + + val jsonObject = runCatching { JsonParser.`object`().from(json) } + .getOrElse { return@initService } + + val name = jsonObject.getString("name") + val url = jsonObject.getString("url") + ServiceList.PeerTube.instance = PeertubeInstance(url, name) + } + } + + @JvmStatic + fun initServices(context: Context) { + ServiceList.all().forEach { initService(context, it.serviceId) } + } +} From 2c7654a579357f0a83edd5c1de26c8f73f149a04 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 12:54:36 +0200 Subject: [PATCH 6/9] Covert newpipe/util/DependentPreferenceHelper to kotlin --- .../util/DependentPreferenceHelper.java | 51 ------------------- .../newpipe/util/DependentPreferenceHelper.kt | 46 +++++++++++++++++ 2 files changed, 46 insertions(+), 51 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.java b/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.java deleted file mode 100644 index 9591beddb..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.schabi.newpipe.util; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -import org.schabi.newpipe.R; - -/** - * For preferences with dependencies and multiple use case, - * this class can be used to reduce the lines of code. - */ -public final class DependentPreferenceHelper { - - private DependentPreferenceHelper() { - // no instance - } - - /** - * Option `Resume playback` depends on `Watch history`, this method can be used to retrieve if - * `Resume playback` and its dependencies are all enabled. - * - * @param context the Android context - * @return returns true if `Resume playback` and `Watch history` are both enabled - */ - public static boolean getResumePlaybackEnabled(final Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - return prefs.getBoolean(context.getString( - R.string.enable_watch_history_key), true) - && prefs.getBoolean(context.getString( - R.string.enable_playback_resume_key), true); - } - - /** - * Option `Position in lists` depends on `Watch history`, this method can be used to retrieve if - * `Position in lists` and its dependencies are all enabled. - * - * @param context the Android context - * @return returns true if `Positions in lists` and `Watch history` are both enabled - */ - public static boolean getPositionsInListsEnabled(final Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - return prefs.getBoolean(context.getString( - R.string.enable_watch_history_key), true) - && prefs.getBoolean(context.getString( - R.string.enable_playback_state_lists_key), true); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.kt b/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.kt new file mode 100644 index 000000000..8e73a94f2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/DependentPreferenceHelper.kt @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2023-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import android.content.Context +import androidx.preference.PreferenceManager +import org.schabi.newpipe.R + +/** + * For preferences with dependencies and multiple use case, + * this class can be used to reduce the lines of code. + */ +object DependentPreferenceHelper { + /** + * Option `Resume playback` depends on `Watch history`, this method can be used to retrieve if + * `Resume playback` and its dependencies are all enabled. + * + * @param context the Android context + * @return returns true if `Resume playback` and `Watch history` are both enabled + */ + @JvmStatic + fun getResumePlaybackEnabled(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true) && + prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true) + } + + /** + * Option `Position in lists` depends on `Watch history`, this method can be used to retrieve if + * `Position in lists` and its dependencies are all enabled. + * + * @param context the Android context + * @return returns true if `Positions in lists` and `Watch history` are both enabled + */ + @JvmStatic + fun getPositionsInListsEnabled(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true) && + prefs.getBoolean(context.getString(R.string.enable_playback_state_lists_key), true) + } +} From 1eedfd7eee3d45640caa485b77df50d17ba57dd2 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 13:11:29 +0200 Subject: [PATCH 7/9] Convert /newpipe/util/StreamTypeUtil to kotlin --- .../schabi/newpipe/util/StreamTypeUtil.java | 50 ----------------- .../org/schabi/newpipe/util/StreamTypeUtil.kt | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 50 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.kt diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java deleted file mode 100644 index 0cc0ecf1f..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.schabi.newpipe.util; - -import org.schabi.newpipe.extractor.stream.StreamType; - -/** - * Utility class for {@link StreamType}. - */ -public final class StreamTypeUtil { - private StreamTypeUtil() { - // No impl pls - } - - /** - * Check if the {@link StreamType} of a stream is a livestream. - * - * @param streamType the stream type of the stream - * @return whether the stream type is {@link StreamType#AUDIO_STREAM}, - * {@link StreamType#AUDIO_LIVE_STREAM} or {@link StreamType#POST_LIVE_AUDIO_STREAM} - */ - public static boolean isAudio(final StreamType streamType) { - return streamType == StreamType.AUDIO_STREAM - || streamType == StreamType.AUDIO_LIVE_STREAM - || streamType == StreamType.POST_LIVE_AUDIO_STREAM; - } - - /** - * Check if the {@link StreamType} of a stream is a livestream. - * - * @param streamType the stream type of the stream - * @return whether the stream type is {@link StreamType#VIDEO_STREAM}, - * {@link StreamType#LIVE_STREAM} or {@link StreamType#POST_LIVE_STREAM} - */ - public static boolean isVideo(final StreamType streamType) { - return streamType == StreamType.VIDEO_STREAM - || streamType == StreamType.LIVE_STREAM - || streamType == StreamType.POST_LIVE_STREAM; - } - - /** - * Check if the {@link StreamType} of a stream is a livestream. - * - * @param streamType the stream type of the stream - * @return whether the stream type is {@link StreamType#LIVE_STREAM} or - * {@link StreamType#AUDIO_LIVE_STREAM} - */ - public static boolean isLiveStream(final StreamType streamType) { - return streamType == StreamType.LIVE_STREAM - || streamType == StreamType.AUDIO_LIVE_STREAM; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.kt b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.kt new file mode 100644 index 000000000..2401a4f49 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.kt @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2021-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.util + +import org.schabi.newpipe.extractor.stream.StreamType + +/** + * Utility class for [StreamType]. + */ +object StreamTypeUtil { + /** + * Check if the [StreamType] of a stream is a livestream. + * + * @param streamType the stream type of the stream + * @return whether the stream type is [StreamType.AUDIO_STREAM], + * [StreamType.AUDIO_LIVE_STREAM] or [StreamType.POST_LIVE_AUDIO_STREAM] + */ + @JvmStatic + fun isAudio(streamType: StreamType): Boolean { + return streamType == StreamType.AUDIO_STREAM || + streamType == StreamType.AUDIO_LIVE_STREAM || + streamType == StreamType.POST_LIVE_AUDIO_STREAM + } + + /** + * Check if the [StreamType] of a stream is a livestream. + * + * @param streamType the stream type of the stream + * @return whether the stream type is [StreamType.VIDEO_STREAM], + * [StreamType.LIVE_STREAM] or [StreamType.POST_LIVE_STREAM] + */ + @JvmStatic + fun isVideo(streamType: StreamType): Boolean { + return streamType == StreamType.VIDEO_STREAM || + streamType == StreamType.LIVE_STREAM || + streamType == StreamType.POST_LIVE_STREAM + } + + /** + * Check if the [StreamType] of a stream is a livestream. + * + * @param streamType the stream type of the stream + * @return whether the stream type is [StreamType.LIVE_STREAM] or + * [StreamType.AUDIO_LIVE_STREAM] + */ + @JvmStatic + fun isLiveStream(streamType: StreamType): Boolean { + return streamType == StreamType.LIVE_STREAM || + streamType == StreamType.AUDIO_LIVE_STREAM + } +} From c6fc94e7bd65e15315e94a66f001c7c29d4cbc42 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Fri, 9 Jan 2026 15:26:43 +0200 Subject: [PATCH 8/9] Convert newpipe/settings/export/PreferencesObjectInputStream to kotlin --- .../export/PreferencesObjectInputStream.java | 58 ------------------- .../export/PreferencesObjectInputStream.kt | 52 +++++++++++++++++ 2 files changed, 52 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java create mode 100644 app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.kt diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java b/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java deleted file mode 100644 index 0d11b0b61..000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.schabi.newpipe.settings.export; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectStreamClass; -import java.util.Set; - -/** - * An {@link ObjectInputStream} that only allows preferences-related types to be deserialized, to - * prevent injections. The only allowed types are: all primitive types, all boxed primitive types, - * null, strings. HashMap, HashSet and arrays of previously defined types are also allowed. Sources: - * - * cmu.edu - * , - * - * OWASP cheatsheet - * , - * - * Apache's {@code ValidatingObjectInputStream} - * - */ -public class PreferencesObjectInputStream extends ObjectInputStream { - - /** - * Primitive types, strings and other built-in types do not pass through resolveClass() but - * instead have a custom encoding; see - * - * official docs. - */ - private static final Set CLASS_WHITELIST = Set.of( - "java.lang.Boolean", - "java.lang.Byte", - "java.lang.Character", - "java.lang.Short", - "java.lang.Integer", - "java.lang.Long", - "java.lang.Float", - "java.lang.Double", - "java.lang.Void", - "java.util.HashMap", - "java.util.HashSet" - ); - - public PreferencesObjectInputStream(final InputStream in) throws IOException { - super(in); - } - - @Override - protected Class resolveClass(final ObjectStreamClass desc) - throws ClassNotFoundException, IOException { - if (CLASS_WHITELIST.contains(desc.getName())) { - return super.resolveClass(desc); - } else { - throw new ClassNotFoundException("Class not allowed: " + desc.getName()); - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.kt b/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.kt new file mode 100644 index 000000000..5f564a7a4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/export/PreferencesObjectInputStream.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.settings.export + +import java.io.IOException +import java.io.InputStream +import java.io.ObjectInputStream +import java.io.ObjectStreamClass + +/** + * An [ObjectInputStream] that only allows preferences-related types to be deserialized, to + * prevent injections. The only allowed types are: all primitive types, all boxed primitive types, + * null, strings. HashMap, HashSet and arrays of previously defined types are also allowed. Sources: + * [cmu.edu](https://wiki.sei.cmu.edu/confluence/display/java/SER00-J.+Enable+serialization+compatibility+during+class+evolution) * , + * [OWASP cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#harden-your-own-javaioobjectinputstream) * , + * [Apache's `ValidatingObjectInputStream`](https://commons.apache.org/proper/commons-io/apidocs/src-html/org/apache/commons/io/serialization/ValidatingObjectInputStream.html#line-118) * + */ +class PreferencesObjectInputStream(stream: InputStream) : ObjectInputStream(stream) { + @Throws(ClassNotFoundException::class, IOException::class) + override fun resolveClass(desc: ObjectStreamClass): Class<*> { + if (desc.name in CLASS_WHITELIST) { + return super.resolveClass(desc) + } else { + throw ClassNotFoundException("Class not allowed: $desc.name") + } + } + + companion object { + /** + * Primitive types, strings and other built-in types do not pass through resolveClass() but + * instead have a custom encoding; see + * [ + * official docs](https://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html#10152). + */ + private val CLASS_WHITELIST = setOf( + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.Character", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", + "java.lang.Void", + "java.util.HashMap", + "java.util.HashSet" + ) + } +} From 224a5d0cb9d4682986ba060b60bd7ff2bfc77482 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Tue, 10 Feb 2026 16:32:53 +0800 Subject: [PATCH 9/9] Minor improvements - Use early return in case of nulls - Use better variable names - Remove non-required newlines, imports and add missing ones Signed-off-by: Aayush Gupta --- .../java/org/schabi/newpipe/ExitActivity.kt | 4 +--- .../newpipe/util/NewPipeTextViewHelper.kt | 2 +- .../org/schabi/newpipe/util/PeertubeHelper.kt | 13 ++++++------- .../org/schabi/newpipe/util/ServiceHelper.kt | 19 +++++++------------ 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.kt b/app/src/main/java/org/schabi/newpipe/ExitActivity.kt index 854a07c43..cc9f448b7 100644 --- a/app/src/main/java/org/schabi/newpipe/ExitActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.kt @@ -10,13 +10,12 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import org.schabi.newpipe.util.NavigationHelper + class ExitActivity : Activity() { @SuppressLint("NewApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - finishAndRemoveTask() - NavigationHelper.restartApp(this) } @@ -24,7 +23,6 @@ class ExitActivity : Activity() { @JvmStatic fun exitAndRemoveFromRecentApps(activity: Activity) { val intent = Intent(activity, ExitActivity::class.java) - intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS diff --git a/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt b/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt index 33046c967..159791813 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/util/NewPipeTextViewHelper.kt @@ -54,7 +54,7 @@ object NewPipeTextViewHelper { selectedText: CharSequence? ) { if (!selectedText.isNullOrEmpty()) { - ShareUtils.shareText(textView.getContext(), "", selectedText.toString()) + ShareUtils.shareText(textView.context, "", selectedText.toString()) } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt index 3da7958d2..9cf3c1e73 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.kt @@ -16,14 +16,17 @@ import org.schabi.newpipe.extractor.ServiceList import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance object PeertubeHelper { + + @JvmStatic + val currentInstance: PeertubeInstance + get() = ServiceList.PeerTube.instance + @JvmStatic fun getInstanceList(context: Context): List { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val savedInstanceListKey = context.getString(R.string.peertube_instance_list_key) val savedJson = sharedPreferences.getString(savedInstanceListKey, null) - if (savedJson == null) { - return listOf(currentInstance) - } + ?: return listOf(currentInstance) return runCatching { JsonParser.`object`().from(savedJson).getArray("instances") @@ -46,8 +49,4 @@ object PeertubeHelper { ServiceList.PeerTube.instance = instance return instance } - - @JvmStatic - val currentInstance: PeertubeInstance - get() = ServiceList.PeerTube.instance } diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt index 99cae0010..4239f43e0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.kt @@ -10,15 +10,12 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.edit import androidx.preference.PreferenceManager -import com.grack.nanojson.JsonObject import com.grack.nanojson.JsonParser -import com.grack.nanojson.JsonParserException import java.util.concurrent.TimeUnit import org.schabi.newpipe.R import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.ServiceList import org.schabi.newpipe.extractor.StreamingService -import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance import org.schabi.newpipe.ktx.getStringSafe @@ -133,8 +130,8 @@ object ServiceHelper { } private fun setSelectedServicePreferences(context: Context, serviceName: String?) { - val sp = PreferenceManager.getDefaultSharedPreferences(context) - sp.edit { putString(context.getString(R.string.current_service_key), serviceName) } + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + sharedPreferences.edit { putString(context.getString(R.string.current_service_key), serviceName) } } @JvmStatic @@ -152,17 +149,15 @@ object ServiceHelper { val json = sharedPreferences.getString( context.getString(R.string.peertube_selected_instance_key), null - ) - if (null == json) { - return - } + ) ?: return val jsonObject = runCatching { JsonParser.`object`().from(json) } .getOrElse { return@initService } - val name = jsonObject.getString("name") - val url = jsonObject.getString("url") - ServiceList.PeerTube.instance = PeertubeInstance(url, name) + ServiceList.PeerTube.instance = PeertubeInstance( + jsonObject.getString("url"), + jsonObject.getString("name") + ) } }