diff --git a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 55b2c770860..f48be553f6c 100644
--- a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -5,6 +5,9 @@
import androidx.preference.Preference;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
+import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.PicassoHelper;
import leakcanary.LeakCanary;
@@ -20,10 +23,16 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
= findPreference(getString(R.string.show_image_indicators_key));
final Preference crashTheAppPreference
= findPreference(getString(R.string.crash_the_app_key));
+ final Preference showErrorSnackbarPreference
+ = findPreference(getString(R.string.show_error_snackbar_key));
+ final Preference createErrorNotificationPreference
+ = findPreference(getString(R.string.create_error_notification_key));
assert showMemoryLeaksPreference != null;
assert showImageIndicatorsPreference != null;
assert crashTheAppPreference != null;
+ assert showErrorSnackbarPreference != null;
+ assert createErrorNotificationPreference != null;
showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> {
startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent());
@@ -38,5 +47,17 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
throw new RuntimeException();
});
+
+ showErrorSnackbarPreference.setOnPreferenceClickListener(preference -> {
+ ErrorUtil.showUiErrorSnackbar(DebugSettingsFragment.this,
+ "Dummy", new RuntimeException("Dummy"));
+ return true;
+ });
+
+ createErrorNotificationPreference.setOnPreferenceClickListener(preference -> {
+ ErrorUtil.createNotification(requireContext(),
+ new ErrorInfo(new RuntimeException("Dummy"), UserAction.UI_ERROR, "Dummy"));
+ return true;
+ });
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index 3785249b451..6c02b6f5750 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -16,8 +16,8 @@
import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
@@ -217,7 +217,7 @@ protected void initACRA() {
ACRA.init(this, acraConfig);
} catch (final ACRAConfigurationException exception) {
exception.printStackTrace();
- ErrorActivity.reportError(this, new ErrorInfo(exception,
+ ErrorUtil.openActivity(this, new ErrorInfo(exception,
UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
}
}
@@ -227,28 +227,35 @@ private void initNotificationChannels() {
// the main and update channels
final NotificationChannelCompat mainChannel = new NotificationChannelCompat
.Builder(getString(R.string.notification_channel_id),
- NotificationManagerCompat.IMPORTANCE_LOW)
+ NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.notification_channel_name))
.setDescription(getString(R.string.notification_channel_description))
.build();
final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat
.Builder(getString(R.string.app_update_notification_channel_id),
- NotificationManagerCompat.IMPORTANCE_LOW)
+ NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.app_update_notification_channel_name))
.setDescription(getString(R.string.app_update_notification_channel_description))
.build();
final NotificationChannelCompat hashChannel = new NotificationChannelCompat
.Builder(getString(R.string.hash_channel_id),
- NotificationManagerCompat.IMPORTANCE_HIGH)
+ NotificationManagerCompat.IMPORTANCE_HIGH)
.setName(getString(R.string.hash_channel_name))
.setDescription(getString(R.string.hash_channel_description))
.build();
+ final NotificationChannelCompat errorReportChannel = new NotificationChannelCompat
+ .Builder(getString(R.string.error_report_channel_id),
+ NotificationManagerCompat.IMPORTANCE_LOW)
+ .setName(getString(R.string.error_report_channel_name))
+ .setDescription(getString(R.string.error_report_channel_description))
+ .build();
+
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.createNotificationChannelsCompat(Arrays.asList(mainChannel,
- appUpdateChannel, hashChannel));
+ appUpdateChannel, hashChannel, errorReportChannel));
}
protected boolean isDisposedRxExceptionsReported() {
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index 9e43394ac3e..122660d6431 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -21,8 +21,8 @@
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
@@ -64,7 +64,7 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
application.getPackageName());
} catch (final PackageManager.NameNotFoundException e) {
- ErrorActivity.reportError(application, new ErrorInfo(e,
+ ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
return "";
}
@@ -79,7 +79,7 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
- ErrorActivity.reportError(application, new ErrorInfo(e,
+ ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
return "";
}
@@ -89,7 +89,7 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a
final byte[] publicKey = md.digest(c.getEncoded());
return byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
- ErrorActivity.reportError(application, new ErrorInfo(e,
+ ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
return "";
}
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 0a49e00e4ad..95663ea0af1 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -63,7 +63,7 @@
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -157,7 +157,7 @@ protected void onCreate(final Bundle savedInstanceState) {
try {
setupDrawer();
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Setting up drawer", e);
}
if (DeviceUtils.isTv(this)) {
@@ -214,7 +214,7 @@ public void onDrawerClosed(final View drawerView) {
/**
* Builds the drawer menu for the current service.
*
- * @throws ExtractionException
+ * @throws ExtractionException if the service didn't provide available kiosks
*/
private void addDrawerMenuForCurrentService() throws ExtractionException {
//Tabs
@@ -266,7 +266,7 @@ private boolean drawerItemSelected(final MenuItem item) {
try {
tabSelected(item);
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e);
}
break;
case R.id.menu_options_about_group:
@@ -372,7 +372,7 @@ private void toggleServices() {
try {
addDrawerMenuForCurrentService();
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Showing main page tabs", e);
}
}
}
@@ -475,7 +475,7 @@ protected void onResume() {
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
getString(R.string.drawer_header_description) + selectedServiceName);
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
}
final SharedPreferences sharedPreferences
@@ -785,7 +785,7 @@ private void handleIntent(final Intent intent) {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Handling intent", e);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 4e96f3bb6e5..9d6e44f045b 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -37,8 +37,8 @@
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.download.DownloadDialog;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.Info;
@@ -231,7 +231,7 @@ private static void handleError(final Context context, final ErrorInfo errorInfo
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else {
- ErrorActivity.reportError(context, errorInfo);
+ ErrorUtil.createNotification(context, errorInfo);
}
if (context instanceof RouterActivity) {
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index a7f5b938f5b..69e975a49f6 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -41,8 +41,8 @@
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.DownloadDialogBinding;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
@@ -402,7 +402,7 @@ private void fetchStreamsSize() {
== R.id.video_button) {
setupVideoSpinner();
}
- }, throwable -> ErrorActivity.reportErrorInSnackbar(context,
+ }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size",
currentInfo.getServiceId()))));
@@ -412,7 +412,7 @@ private void fetchStreamsSize() {
== R.id.audio_button) {
setupAudioSpinner();
}
- }, throwable -> ErrorActivity.reportErrorInSnackbar(context,
+ }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading audio stream size",
currentInfo.getServiceId()))));
@@ -422,7 +422,7 @@ private void fetchStreamsSize() {
== R.id.subtitle_button) {
setupSubtitleSpinner();
}
- }, throwable -> ErrorActivity.reportErrorInSnackbar(context,
+ }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading subtitle stream size",
currentInfo.getServiceId()))));
@@ -799,7 +799,7 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
mainStorage.getTag());
}
} catch (final Exception e) {
- ErrorActivity.reportErrorInSnackbar(this,
+ ErrorUtil.createNotification(requireContext(),
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
return;
}
diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java
index 60d4908ebc0..4d99663643d 100644
--- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java
+++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java
@@ -33,12 +33,11 @@ public class AcraReportSender implements ReportSender {
@Override
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
- ErrorActivity.reportError(context, new ErrorInfo(
+ ErrorUtil.openActivity(context, new ErrorInfo(
new String[]{report.getString(ReportField.STACK_TRACE)},
UserAction.UI_ERROR,
ErrorInfo.SERVICE_NONE,
"ACRA report",
- R.string.app_ui_crash,
- null));
+ R.string.app_ui_crash));
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java b/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java
deleted file mode 100644
index db94de5e5c3..00000000000
--- a/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.schabi.newpipe.error;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Ensures that a Exception is serializable.
- * This is
- */
-public final class EnsureExceptionSerializable {
- private static final String TAG = "EnsureExSerializable";
-
- private EnsureExceptionSerializable() {
- // No instance
- }
-
- /**
- * Ensures that an exception is serializable.
- *
- * If that is not the case a {@link WorkaroundNotSerializableException} is created.
- *
- * @param exception
- * @return if an exception is not serializable a new {@link WorkaroundNotSerializableException}
- * otherwise the exception from the parameter
- */
- public static Exception ensureSerializable(@NonNull final Exception exception) {
- return checkIfSerializable(exception)
- ? exception
- : WorkaroundNotSerializableException.create(exception);
- }
-
- public static boolean checkIfSerializable(@NonNull final Exception exception) {
- try {
- // Check by creating a new ObjectOutputStream which does the serialization
- try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos)
- ) {
- oos.writeObject(exception);
- oos.flush();
-
- bos.toByteArray();
- }
-
- return true;
- } catch (final IOException ex) {
- Log.d(TAG, "Exception is not serializable", ex);
- return false;
- }
- }
-
- public static class WorkaroundNotSerializableException extends Exception {
- protected WorkaroundNotSerializableException(
- final Throwable notSerializableException,
- final Throwable cause) {
- super(notSerializableException.toString(), cause);
- setStackTrace(notSerializableException.getStackTrace());
- }
-
- protected WorkaroundNotSerializableException(final Throwable notSerializableException) {
- super(notSerializableException.toString());
- setStackTrace(notSerializableException.getStackTrace());
- }
-
- public static WorkaroundNotSerializableException create(
- @NonNull final Exception notSerializableException
- ) {
- // Build a list of the exception + all causes
- final List throwableList = new ArrayList<>();
-
- int pos = 0;
- Throwable throwableToProcess = notSerializableException;
-
- while (throwableToProcess != null) {
- throwableList.add(throwableToProcess);
-
- pos++;
- throwableToProcess = throwableToProcess.getCause();
- }
-
- // Reverse list so that it starts with the last one
- Collections.reverse(throwableList);
-
- // Build exception stack
- WorkaroundNotSerializableException cause = null;
- for (final Throwable t : throwableList) {
- cause = cause == null
- ? new WorkaroundNotSerializableException(t)
- : new WorkaroundNotSerializableException(t, cause);
- }
-
- return cause;
- }
-
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
index db3a92d4f93..bd843029687 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
@@ -1,9 +1,10 @@
package org.schabi.newpipe.error;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -11,15 +12,12 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import com.google.android.material.snackbar.Snackbar;
import com.grack.nanojson.JsonWriter;
import org.schabi.newpipe.BuildConfig;
@@ -27,15 +25,13 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityErrorBinding;
import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
/*
* Created by Christian Schabesberger on 24.10.15.
*
@@ -56,6 +52,10 @@
* along with NewPipe. If not, see .
*/
+/**
+ * This activity is used to show error details and allow reporting them in various ways. Use {@link
+ * ErrorUtil#openActivity(Context, ErrorInfo)} to correctly open this activity.
+ */
public class ErrorActivity extends AppCompatActivity {
// LOG TAGS
public static final String TAG = ErrorActivity.class.toString();
@@ -77,67 +77,6 @@ public class ErrorActivity extends AppCompatActivity {
private ActivityErrorBinding activityErrorBinding;
- /**
- * Reports a new error by starting a new activity.
- *
- * Ensure that the data within errorInfo is serializable otherwise
- * an exception will be thrown!
- * {@link EnsureExceptionSerializable} might help.
- *
- * @param context
- * @param errorInfo
- */
- public static void reportError(final Context context, final ErrorInfo errorInfo) {
- final Intent intent = new Intent(context, ErrorActivity.class);
- intent.putExtra(ERROR_INFO, errorInfo);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- }
-
- public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) {
- final View rootView = context instanceof Activity
- ? ((Activity) context).findViewById(android.R.id.content) : null;
- reportErrorInSnackbar(context, rootView, errorInfo);
- }
-
- public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) {
- View rootView = fragment.getView();
- if (rootView == null && fragment.getActivity() != null) {
- rootView = fragment.getActivity().findViewById(android.R.id.content);
- }
- reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo);
- }
-
- public static void reportUiErrorInSnackbar(final Context context,
- final String request,
- final Throwable throwable) {
- reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
- }
-
- public static void reportUiErrorInSnackbar(final Fragment fragment,
- final String request,
- final Throwable throwable) {
- reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
- }
-
-
- ////////////////////////////////////////////////////////////////////////
- // Utils
- ////////////////////////////////////////////////////////////////////////
-
- private static void reportErrorInSnackbar(final Context context,
- @Nullable final View rootView,
- final ErrorInfo errorInfo) {
- if (rootView != null) {
- Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
- .setActionTextColor(Color.YELLOW)
- .setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
- reportError(context, errorInfo)).show();
- } else {
- reportError(context, errorInfo);
- }
- }
-
////////////////////////////////////////////////////////////////////////
// Activity lifecycle
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
index 6581b5752da..b2ba912ecfa 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
@@ -2,6 +2,8 @@ package org.schabi.newpipe.error
import android.os.Parcelable
import androidx.annotation.StringRes
+import com.google.android.exoplayer2.ExoPlaybackException
+import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info
@@ -21,11 +23,14 @@ class ErrorInfo(
val userAction: UserAction,
val serviceName: String,
val request: String,
- val messageStringId: Int,
- @Transient // no need to store throwable, all data for report is in other variables
- var throwable: Throwable? = null
+ val messageStringId: Int
) : Parcelable {
+ // no need to store throwable, all data for report is in other variables
+ // also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302
+ @IgnoredOnParcel
+ var throwable: Throwable? = null
+
private constructor(
throwable: Throwable,
userAction: UserAction,
@@ -36,9 +41,10 @@ class ErrorInfo(
userAction,
serviceName,
request,
- getMessageStringId(throwable, userAction),
- throwable
- )
+ getMessageStringId(throwable, userAction)
+ ) {
+ this.throwable = throwable
+ }
private constructor(
throwable: List,
@@ -50,9 +56,10 @@ class ErrorInfo(
userAction,
serviceName,
request,
- getMessageStringId(throwable.firstOrNull(), userAction),
- throwable.firstOrNull()
- )
+ getMessageStringId(throwable.firstOrNull(), userAction)
+ ) {
+ this.throwable = throwable.firstOrNull()
+ }
// constructors with single throwable
constructor(throwable: Throwable, userAction: UserAction, request: String) :
@@ -102,6 +109,13 @@ class ErrorInfo(
throwable is ContentNotSupportedException -> R.string.content_not_supported
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
throwable is ExtractionException -> R.string.parsing_error
+ throwable is ExoPlaybackException -> {
+ when (throwable.type) {
+ ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure
+ ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure
+ else -> R.string.player_unrecoverable_failure
+ }
+ }
action == UserAction.UI_ERROR -> R.string.app_ui_crash
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt
index 228c17f8cd6..692cb427afc 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt
@@ -118,7 +118,7 @@ class ErrorPanelHelper(
showAndSetErrorButtonAction(
R.string.error_snackbar_action
) {
- ErrorActivity.reportError(context, errorInfo)
+ ErrorUtil.openActivity(context, errorInfo)
}
errorTextView.setText(getExceptionDescription(errorInfo.throwable))
@@ -178,7 +178,7 @@ class ErrorPanelHelper(
val DEBUG: Boolean = MainActivity.DEBUG
@StringRes
- public fun getExceptionDescription(throwable: Throwable?): Int {
+ fun getExceptionDescription(throwable: Throwable?): Int {
return when (throwable) {
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
is GeographicRestrictionException -> R.string.georestricted_content
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
new file mode 100644
index 00000000000..3fd743c6991
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
@@ -0,0 +1,165 @@
+package org.schabi.newpipe.error
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.os.Build
+import android.view.View
+import android.widget.Toast
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.google.android.material.snackbar.Snackbar
+import org.schabi.newpipe.R
+
+/**
+ * This class contains all of the methods that should be used to let the user know that an error has
+ * occurred in the least intrusive way possible for each case. This class is for unexpected errors,
+ * for handled errors (e.g. network errors) use e.g. [ErrorPanelHelper] instead.
+ * - Use a snackbar if the exception is not critical and it happens in a place where a root view
+ * is available.
+ * - Use a notification if the exception happens inside a background service (player, subscription
+ * import, ...) or there is no activity/fragment from which to extract a root view.
+ * - Finally use the error activity only as a last resort in case the exception is critical and
+ * happens in an open activity (since the workflow would be interrupted anyway in that case).
+ */
+class ErrorUtil {
+ companion object {
+ private const val ERROR_REPORT_NOTIFICATION_ID = 5340681
+
+ /**
+ * Starts a new error activity allowing the user to report the provided error. Only use this
+ * method directly as a last resort in case the exception is critical and happens in an open
+ * activity (since the workflow would be interrupted anyway in that case). So never use this
+ * for background services.
+ *
+ * @param context the context to use to start the new activity
+ * @param errorInfo the error info to be reported
+ */
+ @JvmStatic
+ fun openActivity(context: Context, errorInfo: ErrorInfo) {
+ context.startActivity(getErrorActivityIntent(context, errorInfo))
+ }
+
+ /**
+ * Show a bottom snackbar to the user, with a report button that opens the error activity.
+ * Use this method if the exception is not critical and it happens in a place where a root
+ * view is available.
+ *
+ * @param context will be used to obtain the root view if it is an [Activity]; if no root
+ * view can be found an error notification is shown instead
+ * @param errorInfo the error info to be reported
+ */
+ @JvmStatic
+ fun showSnackbar(context: Context, errorInfo: ErrorInfo) {
+ val rootView = if (context is Activity) context.findViewById(R.id.content) else null
+ showSnackbar(context, rootView, errorInfo)
+ }
+
+ /**
+ * Show a bottom snackbar to the user, with a report button that opens the error activity.
+ * Use this method if the exception is not critical and it happens in a place where a root
+ * view is available.
+ *
+ * @param fragment will be used to obtain the root view if it has a connected [Activity]; if
+ * no root view can be found an error notification is shown instead
+ * @param errorInfo the error info to be reported
+ */
+ @JvmStatic
+ fun showSnackbar(fragment: Fragment, errorInfo: ErrorInfo) {
+ var rootView = fragment.view
+ if (rootView == null && fragment.activity != null) {
+ rootView = fragment.requireActivity().findViewById(R.id.content)
+ }
+ showSnackbar(fragment.requireContext(), rootView, errorInfo)
+ }
+
+ /**
+ * Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
+ */
+ @JvmStatic
+ fun showUiErrorSnackbar(context: Context, request: String, throwable: Throwable) {
+ showSnackbar(context, ErrorInfo(throwable, UserAction.UI_ERROR, request))
+ }
+
+ /**
+ * Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
+ */
+ @JvmStatic
+ fun showUiErrorSnackbar(fragment: Fragment, request: String, throwable: Throwable) {
+ showSnackbar(fragment, ErrorInfo(throwable, UserAction.UI_ERROR, request))
+ }
+
+ /**
+ * Create an error notification. Tapping on the notification opens the error activity. Use
+ * this method if the exception happens inside a background service (player, subscription
+ * import, ...) or there is no activity/fragment from which to extract a root view.
+ *
+ * @param context the context to use to show the notification
+ * @param errorInfo the error info to be reported; the error message
+ * [ErrorInfo.messageStringId] will be shown in the notification
+ * description
+ */
+ @JvmStatic
+ fun createNotification(context: Context, errorInfo: ErrorInfo) {
+ val notificationManager =
+ ContextCompat.getSystemService(context, NotificationManager::class.java)
+ if (notificationManager == null) {
+ // this should never happen, but just in case open error activity
+ openActivity(context, errorInfo)
+ }
+
+ var pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE
+ }
+
+ val notificationBuilder: NotificationCompat.Builder =
+ NotificationCompat.Builder(
+ context,
+ context.getString(R.string.error_report_channel_id)
+ )
+ .setSmallIcon(R.drawable.ic_bug_report)
+ .setContentTitle(context.getString(R.string.error_report_notification_title))
+ .setContentText(context.getString(errorInfo.messageStringId))
+ .setAutoCancel(true)
+ .setContentIntent(
+ PendingIntent.getActivity(
+ context,
+ 0,
+ getErrorActivityIntent(context, errorInfo),
+ pendingIntentFlags
+ )
+ )
+
+ notificationManager!!.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
+
+ // since the notification is silent, also show a toast, otherwise the user is confused
+ Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT)
+ .show()
+ }
+
+ private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent {
+ val intent = Intent(context, ErrorActivity::class.java)
+ intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ private fun showSnackbar(context: Context, rootView: View?, errorInfo: ErrorInfo) {
+ if (rootView == null) {
+ // fallback to showing a notification if no root view is available
+ createNotification(context, errorInfo)
+ } else {
+ Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
+ .setActionTextColor(Color.YELLOW)
+ .setAction(context.getString(R.string.error_snackbar_action).uppercase()) {
+ openActivity(context, errorInfo)
+ }.show()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index db91755dfcb..9b4bf837725 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -7,12 +7,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorPanelHelper;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.util.InfoCache;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -198,9 +199,8 @@ public final boolean isErrorPanelVisible() {
}
/**
- * Show a SnackBar and only call
- * {@link ErrorActivity#reportErrorInSnackbar(androidx.fragment.app.Fragment, ErrorInfo)}
- * IF we a find a valid view (otherwise the error screen appears).
+ * Directly calls {@link ErrorUtil#showSnackbar(Fragment, ErrorInfo)}, that shows a snackbar if
+ * a valid view can be found, otherwise creates an error report notification.
*
* @param errorInfo The error information
*/
@@ -208,6 +208,6 @@ public void showSnackBarError(final ErrorInfo errorInfo) {
if (DEBUG) {
Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
}
- ErrorActivity.reportErrorInSnackbar(this, errorInfo);
+ ErrorUtil.showSnackbar(this, errorInfo);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
index 7e0186e1cc3..de68269e954 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
@@ -23,7 +23,7 @@
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentMainBinding;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
@@ -145,7 +145,7 @@ public boolean onOptionsItemSelected(final MenuItem item) {
NavigationHelper.openSearchFragment(getFM(),
ServiceHelper.getSelectedServiceId(activity), "");
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Opening search fragment", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Opening search fragment", e);
}
return true;
}
@@ -227,16 +227,11 @@ private SelectedTabsPagerAdapter(final Context context,
public Fragment getItem(final int position) {
final Tab tab = internalTabsList.get(position);
- Throwable throwable = null;
- Fragment fragment = null;
+ final Fragment fragment;
try {
fragment = tab.getFragment(context);
} catch (final ExtractionException e) {
- throwable = e;
- }
-
- if (throwable != null) {
- ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable);
+ ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e);
return new BlankFragment();
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 8c6e0153776..b5129f0b13c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -55,8 +55,8 @@
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
import org.schabi.newpipe.download.DownloadDialog;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
@@ -533,7 +533,7 @@ private void openChannel(final String subChannelUrl, final String subChannelName
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
subChannelUrl, subChannelName);
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
}
@@ -1681,9 +1681,8 @@ public void openDownloadDialog() {
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (final Exception e) {
- ErrorActivity.reportErrorInSnackbar(activity,
- new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
- currentInfo));
+ ErrorUtil.showSnackbar(activity, new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG,
+ "Showing download dialog", currentInfo));
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
index b9065c9694d..4319d42ee69 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
@@ -21,7 +21,7 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PignateFooterBinding;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@@ -293,7 +293,7 @@ public void selected(final ChannelInfoItem selectedItem) {
selectedItem.getUrl(),
selectedItem.getName());
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(
+ ErrorUtil.showUiErrorSnackbar(
BaseListFragment.this, "Opening channel fragment", e);
}
}
@@ -309,7 +309,7 @@ public void selected(final PlaylistInfoItem selectedItem) {
selectedItem.getUrl(),
selectedItem.getName());
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this,
+ ErrorUtil.showUiErrorSnackbar(BaseListFragment.this,
"Opening playlist fragment", e);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index 30e38a966a6..37954478d9b 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -26,8 +26,8 @@
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
import org.schabi.newpipe.databinding.FragmentChannelBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
@@ -407,7 +407,7 @@ public void onClick(final View v) {
currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index a8763af7305..a61cec11d91 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -24,8 +24,8 @@
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
@@ -310,7 +310,7 @@ public void handleResult(@NonNull final PlaylistInfo result) {
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
result.getUploaderUrl(), result.getUploaderName());
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
});
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index d4d73f74ff2..ba0bd50c625 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -38,8 +38,8 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.databinding.FragmentSearchBinding;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
@@ -225,8 +225,7 @@ private void updateService() {
try {
service = NewPipe.getService(serviceId);
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this,
- "Getting service for id " + serviceId, e);
+ ErrorUtil.showUiErrorSnackbar(this, "Getting service for id " + serviceId, e);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
index 079efa4a8b4..cb47efa9252 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
@@ -13,7 +13,7 @@
import androidx.appcompat.app.AppCompatActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@@ -171,7 +171,7 @@ private void openCommentAuthor(final CommentsInfoItem item) {
item.getUploaderUrl(),
item.getUploaderName());
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
+ ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java
index 675799586fe..8dbd7b2c5c2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java
@@ -23,8 +23,8 @@
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -86,12 +86,11 @@ public void onCreate(final Bundle savedInstanceState) {
setupServiceVariables();
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
- ErrorActivity.reportErrorInSnackbar(activity,
+ ErrorUtil.showSnackbar(activity,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
NewPipe.getNameOfService(currentServiceId),
"Service does not support importing subscriptions",
- R.string.general_error,
- null));
+ R.string.general_error));
activity.finish();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
index 901eabf4453..b7c11b1605d 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
@@ -35,8 +35,8 @@
import org.reactivestreams.Publisher;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.ktx.ExceptionUtils;
@@ -153,7 +153,7 @@ protected void stopService() {
protected void stopAndReportError(final Throwable throwable, final String request) {
stopService();
- ErrorActivity.reportError(this, new ErrorInfo(
+ ErrorUtil.createNotification(this, new ErrorInfo(
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
}
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 2d8c1a83017..ee09cb8665e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -141,6 +141,9 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
+import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamSegment;
@@ -165,7 +168,6 @@
import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.player.playback.PlayerMediaSession;
import org.schabi.newpipe.player.playback.SurfaceHolderCallback;
-import org.schabi.newpipe.player.playererror.PlayerErrorHandler;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@@ -268,8 +270,6 @@ public final class Player implements
@Nullable private MediaSourceTag currentMetadata;
@Nullable private Bitmap currentThumbnail;
- @NonNull private PlayerErrorHandler playerErrorHandler;
-
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
@@ -413,8 +413,6 @@ public Player(@NonNull final MainPlayer service) {
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
audioResolver = new AudioPlaybackResolver(context, dataSource);
- playerErrorHandler = new PlayerErrorHandler(context);
-
windowManager = ContextCompat.getSystemService(context, WindowManager.class);
}
@@ -2518,29 +2516,30 @@ public void onPlayerError(@NonNull final ExoPlaybackException error) {
saveStreamProgressState();
+ // create error notification
+ final ErrorInfo errorInfo;
+ if (currentMetadata == null) {
+ errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
+ "Player error[type=" + error.type + "] occurred, currentMetadata is null");
+ } else {
+ errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
+ "Player error[type=" + error.type + "] occurred while playing "
+ + currentMetadata.getMetadata().getUrl(),
+ currentMetadata.getMetadata());
+ }
+ ErrorUtil.createNotification(context, errorInfo);
+
switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE:
processSourceError(error.getSourceException());
- playerErrorHandler.showPlayerError(
- error,
- currentMetadata.getMetadata(),
- R.string.player_stream_failure);
break;
case ExoPlaybackException.TYPE_UNEXPECTED:
- playerErrorHandler.showPlayerError(
- error,
- currentMetadata.getMetadata(),
- R.string.player_recoverable_failure);
setRecovery();
reloadPlayQueueManager();
break;
case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER:
default:
- playerErrorHandler.showPlayerError(
- error,
- currentMetadata.getMetadata(),
- R.string.player_unrecoverable_failure);
onPlaybackShutdown();
break;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java
deleted file mode 100644
index 626200ae1b0..00000000000
--- a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.schabi.newpipe.player.playererror;
-
-import android.content.Context;
-import android.util.Log;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.preference.PreferenceManager;
-
-import com.google.android.exoplayer2.ExoPlaybackException;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.EnsureExceptionSerializable;
-import org.schabi.newpipe.error.ErrorActivity;
-import org.schabi.newpipe.error.ErrorInfo;
-import org.schabi.newpipe.error.UserAction;
-import org.schabi.newpipe.extractor.Info;
-
-/**
- * Handles (exoplayer)errors that occur in the player.
- */
-public class PlayerErrorHandler {
- // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
- // or it fails with an IllegalArgumentException
- // https://stackoverflow.com/a/54744028
- private static final String TAG = "PlayerErrorHandler";
-
- @Nullable
- private Toast errorToast;
-
- @NonNull
- private final Context context;
-
- public PlayerErrorHandler(@NonNull final Context context) {
- this.context = context;
- }
-
- public void showPlayerError(
- @NonNull final ExoPlaybackException exception,
- @NonNull final Info info,
- @StringRes final int textResId
- ) {
- // Hide existing toast message
- if (errorToast != null) {
- Log.d(TAG, "Trying to cancel previous player error error toast");
- errorToast.cancel();
- errorToast = null;
- }
-
- if (shouldReportError()) {
- try {
- reportError(exception, info);
- // When a report pops up we need no toast
- return;
- } catch (final Exception ex) {
- Log.w(TAG, "Unable to report error:", ex);
- // This will show the toast as fallback
- }
- }
-
- Log.d(TAG, "Showing player error toast");
- errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT);
- errorToast.show();
- }
-
- private void reportError(@NonNull final ExoPlaybackException exception,
- @NonNull final Info info) {
- ErrorActivity.reportError(
- context,
- new ErrorInfo(
- EnsureExceptionSerializable.ensureSerializable(exception),
- UserAction.PLAY_STREAM,
- "Player error[type=" + exception.type + "] occurred while playing: "
- + info.getUrl(),
- info
- )
- );
- }
-
- private boolean shouldReportError() {
- return PreferenceManager
- .getDefaultSharedPreferences(context)
- .getBoolean(
- context.getString(R.string.report_player_errors_key),
- false);
- }
-}
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 6e7e7593269..794c78d3ea2 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -20,7 +20,7 @@
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry;
@@ -205,7 +205,7 @@ private void exportDatabase(final StoredFileHelper file, final Uri exportDataUri
saveLastImportExportDataUri(exportDataUri); // save export path only on success
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e);
}
}
@@ -247,7 +247,7 @@ private void importDatabase(final StoredFileHelper file, final Uri importDataUri
finishImport(importDataUri);
}
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
index cb6ce263dce..33e0ba16b5b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
@@ -9,8 +9,8 @@
import androidx.preference.Preference;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.InfoCache;
@@ -64,7 +64,7 @@ private static Disposable getDeletePlaybackStatesDisposable(
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
- throwable -> ErrorActivity.reportError(context,
+ throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete playback states")));
}
@@ -76,7 +76,7 @@ private static Disposable getWholeStreamHistoryDisposable(
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
- throwable -> ErrorActivity.reportError(context,
+ throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete from history")));
}
@@ -87,7 +87,7 @@ private static Disposable getRemoveOrphanedRecordsDisposable(
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> { },
- throwable -> ErrorActivity.reportError(context,
+ throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Clear orphaned records")));
}
@@ -99,7 +99,7 @@ private static Disposable getDeleteSearchHistoryDisposable(
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.search_history_deleted, Toast.LENGTH_SHORT).show(),
- throwable -> ErrorActivity.reportError(context,
+ throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete search history")));
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index a0105a11f80..116807cbc8e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -16,7 +16,7 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -153,7 +153,7 @@ public void onNext(@NonNull final List newSubscriptions) {
@Override
public void onError(@NonNull final Throwable exception) {
- ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
+ ErrorUtil.showUiErrorSnackbar(SelectChannelFragment.this,
"Loading subscription", exception);
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
index 9d873607614..a766ee0747d 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
@@ -16,7 +16,7 @@
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.util.KioskTranslator;
@@ -48,7 +48,6 @@
*/
public class SelectKioskFragment extends DialogFragment {
- private RecyclerView recyclerView = null;
private SelectKioskAdapter selectKioskAdapter = null;
private OnSelectedListener onSelectedListener = null;
@@ -76,12 +75,12 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
- recyclerView = v.findViewById(R.id.items_list);
+ final RecyclerView recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try {
selectKioskAdapter = new SelectKioskAdapter();
} catch (final Exception e) {
- ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e);
+ ErrorUtil.showUiErrorSnackbar(this, "Selecting kiosk", e);
}
recyclerView.setAdapter(selectKioskAdapter);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index f94e391babe..e8491d52cda 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -1,6 +1,5 @@
package org.schabi.newpipe.settings;
-import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -21,8 +20,8 @@
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
@@ -105,8 +104,7 @@ private void displayPlaylists(final List newPlaylists) {
}
protected void onError(final Throwable e) {
- final Activity activity = requireActivity();
- ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e,
+ ErrorUtil.showSnackbar(requireActivity(), new ErrorInfo(e,
UserAction.UI_ERROR, "Loading playlists"));
}
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 b0b9567d869..8924ecbe12a 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
@@ -8,8 +8,8 @@
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.DeviceUtils;
@@ -157,7 +157,7 @@ public static void initMigrations(final Context context, final boolean isFirstRu
} catch (final Exception e) {
// save the version with the last successful migration and report the error
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
- ErrorActivity.reportError(context, new ErrorInfo(
+ ErrorUtil.openActivity(context, new ErrorInfo(
e,
UserAction.PREFERENCES_MIGRATION,
"Migrating preferences from version " + lastPrefVersion + " to "
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
index c9eb42fca17..95f7f50baf7 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
@@ -27,8 +27,8 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.settings.SelectChannelFragment;
@@ -182,7 +182,7 @@ private void addTab(final int tabId) {
final Tab.Type type = typeFrom(tabId);
if (type == null) {
- ErrorActivity.reportErrorInSnackbar(this,
+ ErrorUtil.showSnackbar(this,
new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
return;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
index a148255b3f2..eac5ce311f5 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
@@ -12,8 +12,8 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem.LocalItemType;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
@@ -506,7 +506,7 @@ private String getDefaultKioskId(final Context context) {
final StreamingService service = NewPipe.getService(kioskServiceId);
kioskId = service.getKioskList().getDefaultKioskId();
} catch (final ExtractionException e) {
- ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e,
+ ErrorUtil.showSnackbar(context, new ErrorInfo(e,
UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
}
return kioskId;
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index 057b9cb0967..39bdefbe0c6 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -39,8 +39,8 @@
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.Localization;
@@ -581,9 +581,9 @@ private void showError(DownloadMission mission, UserAction action, @StringRes in
service = ErrorInfo.SERVICE_NONE;
}
- ErrorActivity.reportError(mContext,
+ ErrorUtil.createNotification(mContext,
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
- service, request.toString(), reason, null));
+ service, request.toString(), reason));
}
public void clearFinishedDownloads(boolean delete) {
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index e2b797576ab..b6f76fce2ac 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -89,8 +89,6 @@
- @string/never
- report_player_errors_key
-
seekbar_preview_thumbnail_key
seekbar_preview_thumbnail_high_quality
seekbar_preview_thumbnail_low_quality
@@ -188,9 +186,11 @@
allow_disposed_exceptions_key
show_original_time_ago_key
disable_media_tunneling_key
- crash_the_app_key
show_image_indicators_key
show_crash_the_player_key
+ crash_the_app_key
+ show_error_snackbar_key
+ create_error_notification_key
theme
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a8bb4c78883..cc518e2bb6f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -53,8 +53,6 @@
Show \"Play with Kodi\" option
Display an option to play a video via Kodi media center
Crash the player
- Report player errors
- Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems)
Scale thumbnail to 1:1 aspect ratio
Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions)
First action button
@@ -182,14 +180,17 @@
Just Once
File
newpipe
- NewPipe Notification
- Notifications for NewPipe background and popup players
+ NewPipe notification
+ Notifications for NewPipe\'s player
newpipeAppUpdate
- App Update Notification
- Notifications for new NewPipe version
+ App update notification
+ Notifications for new NewPipe versions
newpipeHash
- Video Hash Notification
+ Video hash notification
Notifications for video hashing progress
+ newpipeErrorReport
+ Error report notification
+ Notifications to report errors
[Unknown]
Switch to Background
Switch to Popup
@@ -243,6 +244,8 @@
Do you want to restore defaults?
Give permission to display over other apps
+ NewPipe encountered an error, tap to report
+ An error occurred, see the notification
Sorry, that should not have happened.
Guru Meditation.
Report this error via e-mail
@@ -475,9 +478,11 @@
Disable media tunneling if you experience a black screen or stuttering on video playback
Show image indicators
Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory
- Crash the app
Show \"crash the player\"
Shows a crash option when using the player
+ Crash the app
+ Show an error snackbar
+ Create an error notification
Import
Import from
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 5e2cc28edc9..0052125a2f7 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -51,10 +51,9 @@
-
+
+