mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-11-04 01:03:00 +00:00 
			
		
		
		
	Merge pull request #7482 from TeamNewPipe/unify-error-reporting
Unify error reporting and add error notification
This commit is contained in:
		@@ -5,6 +5,9 @@ import android.os.Bundle;
 | 
			
		||||
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 class DebugSettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                = 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 class DebugSettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
        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;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,8 @@ import org.acra.ACRA;
 | 
			
		||||
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 @@ public class App extends MultiDexApplication {
 | 
			
		||||
            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 @@ public class App extends MultiDexApplication {
 | 
			
		||||
        // 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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,8 @@ import com.grack.nanojson.JsonObject;
 | 
			
		||||
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 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
            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 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
            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 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
            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 "";
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding;
 | 
			
		||||
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 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
        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 class MainActivity extends AppCompatActivity {
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
                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 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
            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 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
            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 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
                NavigationHelper.gotoMainFragment(getSupportFragmentManager());
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e);
 | 
			
		||||
            ErrorUtil.showUiErrorSnackbar(this, "Handling intent", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,8 +37,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
 | 
			
		||||
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 @@ public class RouterActivity extends AppCompatActivity {
 | 
			
		||||
        } 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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,8 +41,8 @@ import com.nononsenseapps.filepicker.Utils;
 | 
			
		||||
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 @@ public class DownloadDialog extends DialogFragment
 | 
			
		||||
                            == 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 @@ public class DownloadDialog extends DialogFragment
 | 
			
		||||
                            == 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 @@ public class DownloadDialog extends DialogFragment
 | 
			
		||||
                            == 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 @@ public class DownloadDialog extends DialogFragment
 | 
			
		||||
                        mainStorage.getTag());
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            ErrorActivity.reportErrorInSnackbar(this,
 | 
			
		||||
            ErrorUtil.createNotification(requireContext(),
 | 
			
		||||
                    new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
     * <br/>
 | 
			
		||||
     * 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<Throwable> 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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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.util.Log;
 | 
			
		||||
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.MainActivity;
 | 
			
		||||
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 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
 | 
			
		||||
 * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
     * <br/>
 | 
			
		||||
     * Ensure that the data within errorInfo is serializable otherwise
 | 
			
		||||
     * an exception will be thrown!<br/>
 | 
			
		||||
     * {@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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Throwable>,
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										165
									
								
								app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
									
									
									
									
									
										Normal file
									
								
							@@ -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<View>(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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,12 +7,13 @@ import android.widget.ProgressBar;
 | 
			
		||||
 | 
			
		||||
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 abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
 | 
			
		||||
        if (DEBUG) {
 | 
			
		||||
            Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
 | 
			
		||||
        }
 | 
			
		||||
        ErrorActivity.reportErrorInSnackbar(this, errorInfo);
 | 
			
		||||
        ErrorUtil.showSnackbar(this, errorInfo);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import com.google.android.material.tabs.TabLayout;
 | 
			
		||||
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 class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
 | 
			
		||||
                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 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
 | 
			
		||||
        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();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,8 @@ import org.schabi.newpipe.R;
 | 
			
		||||
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 @@ public final class VideoDetailFragment
 | 
			
		||||
            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 final class VideoDetailFragment
 | 
			
		||||
 | 
			
		||||
            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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import androidx.viewbinding.ViewBinding;
 | 
			
		||||
 | 
			
		||||
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 abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
 | 
			
		||||
                            selectedItem.getUrl(),
 | 
			
		||||
                            selectedItem.getName());
 | 
			
		||||
                } catch (final Exception e) {
 | 
			
		||||
                    ErrorActivity.reportUiErrorInSnackbar(
 | 
			
		||||
                    ErrorUtil.showUiErrorSnackbar(
 | 
			
		||||
                            BaseListFragment.this, "Opening channel fragment", e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -309,7 +309,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
 | 
			
		||||
                            selectedItem.getUrl(),
 | 
			
		||||
                            selectedItem.getName());
 | 
			
		||||
                } catch (final Exception e) {
 | 
			
		||||
                    ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this,
 | 
			
		||||
                    ErrorUtil.showUiErrorSnackbar(BaseListFragment.this,
 | 
			
		||||
                            "Opening playlist fragment", e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
 | 
			
		||||
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 class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
 | 
			
		||||
                                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");
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ import org.schabi.newpipe.R;
 | 
			
		||||
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 class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
 | 
			
		||||
                        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);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,8 @@ import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
 | 
			
		||||
import org.schabi.newpipe.R;
 | 
			
		||||
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;
 | 
			
		||||
@@ -223,8 +223,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import android.widget.TextView;
 | 
			
		||||
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 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
 | 
			
		||||
                    item.getUploaderUrl(),
 | 
			
		||||
                    item.getUploaderName());
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
 | 
			
		||||
            ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@ import androidx.core.text.util.LinkifyCompat;
 | 
			
		||||
 | 
			
		||||
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 class SubscriptionsImportFragment extends BaseFragment {
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,8 @@ import androidx.core.app.ServiceCompat;
 | 
			
		||||
 | 
			
		||||
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 @@ public abstract class BaseImportExportService extends Service {
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -141,6 +141,9 @@ import org.schabi.newpipe.MainActivity;
 | 
			
		||||
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.MediaSourceManager;
 | 
			
		||||
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 final class Player implements
 | 
			
		||||
        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 final class Player implements
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,7 @@ import androidx.preference.PreferenceManager;
 | 
			
		||||
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 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
            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 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                finishImport(importDataUri);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
 | 
			
		||||
            ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@ import androidx.appcompat.app.AlertDialog;
 | 
			
		||||
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 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                .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 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                .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 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                .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 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
 | 
			
		||||
                .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")));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
 | 
			
		||||
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 class SelectChannelFragment extends DialogFragment {
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onError(@NonNull final Throwable exception) {
 | 
			
		||||
                ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
 | 
			
		||||
                ErrorUtil.showUiErrorSnackbar(SelectChannelFragment.this,
 | 
			
		||||
                        "Loading subscription", exception);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
 | 
			
		||||
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 @@ import java.util.Vector;
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
public class SelectKioskFragment extends DialogFragment {
 | 
			
		||||
    private RecyclerView recyclerView = null;
 | 
			
		||||
    private SelectKioskAdapter selectKioskAdapter = null;
 | 
			
		||||
 | 
			
		||||
    private OnSelectedListener onSelectedListener = null;
 | 
			
		||||
@@ -76,12 +75,12 @@ public class SelectKioskFragment extends DialogFragment {
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.LocalItem;
 | 
			
		||||
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 @@ public class SelectPlaylistFragment extends DialogFragment {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ import android.util.Log;
 | 
			
		||||
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 final class SettingMigrations {
 | 
			
		||||
            } 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 "
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@ import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
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 @@ public class ChooseTabsFragment extends Fragment {
 | 
			
		||||
        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;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ import com.grack.nanojson.JsonSink;
 | 
			
		||||
 | 
			
		||||
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 @@ public abstract class Tab {
 | 
			
		||||
                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;
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,8 @@ import com.google.android.material.snackbar.Snackbar;
 | 
			
		||||
 | 
			
		||||
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 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
 | 
			
		||||
            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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -89,8 +89,6 @@
 | 
			
		||||
        <item>@string/never</item>
 | 
			
		||||
    </string-array>
 | 
			
		||||
 | 
			
		||||
    <string name="report_player_errors_key" translatable="false">report_player_errors_key</string>
 | 
			
		||||
 | 
			
		||||
    <string name="seekbar_preview_thumbnail_key" translatable="false">seekbar_preview_thumbnail_key</string>
 | 
			
		||||
    <string name="seekbar_preview_thumbnail_high_quality" translatable="false">seekbar_preview_thumbnail_high_quality</string>
 | 
			
		||||
    <string name="seekbar_preview_thumbnail_low_quality" translatable="false">seekbar_preview_thumbnail_low_quality</string>
 | 
			
		||||
@@ -188,9 +186,11 @@
 | 
			
		||||
    <string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string>
 | 
			
		||||
    <string name="show_original_time_ago_key" translatable="false">show_original_time_ago_key</string>
 | 
			
		||||
    <string name="disable_media_tunneling_key" translatable="false">disable_media_tunneling_key</string>
 | 
			
		||||
    <string name="crash_the_app_key" translatable="false">crash_the_app_key</string>
 | 
			
		||||
    <string name="show_image_indicators_key" translatable="false">show_image_indicators_key</string>
 | 
			
		||||
    <string name="show_crash_the_player_key" translatable="false">show_crash_the_player_key</string>
 | 
			
		||||
    <string name="crash_the_app_key" translatable="false">crash_the_app_key</string>
 | 
			
		||||
    <string name="show_error_snackbar_key" translatable="false">show_error_snackbar_key</string>
 | 
			
		||||
    <string name="create_error_notification_key" translatable="false">create_error_notification_key</string>
 | 
			
		||||
 | 
			
		||||
    <!-- THEMES -->
 | 
			
		||||
    <string name="theme_key" translatable="false">theme</string>
 | 
			
		||||
 
 | 
			
		||||
@@ -53,8 +53,6 @@
 | 
			
		||||
    <string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string>
 | 
			
		||||
    <string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string>
 | 
			
		||||
    <string name="crash_the_player">Crash the player</string>
 | 
			
		||||
    <string name="report_player_errors_title">Report player errors</string>
 | 
			
		||||
    <string name="report_player_errors_summary">Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems)</string>
 | 
			
		||||
    <string name="notification_scale_to_square_image_title">Scale thumbnail to 1:1 aspect ratio</string>
 | 
			
		||||
    <string name="notification_scale_to_square_image_summary">Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions)</string>
 | 
			
		||||
    <string name="notification_action_0_title">First action button</string>
 | 
			
		||||
@@ -182,14 +180,17 @@
 | 
			
		||||
    <string name="just_once">Just Once</string>
 | 
			
		||||
    <string name="file">File</string>
 | 
			
		||||
    <string name="notification_channel_id" translatable="false">newpipe</string>
 | 
			
		||||
    <string name="notification_channel_name">NewPipe Notification</string>
 | 
			
		||||
    <string name="notification_channel_description">Notifications for NewPipe background and popup players</string>
 | 
			
		||||
    <string name="notification_channel_name">NewPipe notification</string>
 | 
			
		||||
    <string name="notification_channel_description">Notifications for NewPipe\'s player</string>
 | 
			
		||||
    <string name="app_update_notification_channel_id" translatable="false">newpipeAppUpdate</string>
 | 
			
		||||
    <string name="app_update_notification_channel_name">App Update Notification</string>
 | 
			
		||||
    <string name="app_update_notification_channel_description">Notifications for new NewPipe version</string>
 | 
			
		||||
    <string name="app_update_notification_channel_name">App update notification</string>
 | 
			
		||||
    <string name="app_update_notification_channel_description">Notifications for new NewPipe versions</string>
 | 
			
		||||
    <string name="hash_channel_id" translatable="false">newpipeHash</string>
 | 
			
		||||
    <string name="hash_channel_name">Video Hash Notification</string>
 | 
			
		||||
    <string name="hash_channel_name">Video hash notification</string>
 | 
			
		||||
    <string name="hash_channel_description">Notifications for video hashing progress</string>
 | 
			
		||||
    <string name="error_report_channel_id" translatable="false">newpipeErrorReport</string>
 | 
			
		||||
    <string name="error_report_channel_name">Error report notification</string>
 | 
			
		||||
    <string name="error_report_channel_description">Notifications to report errors</string>
 | 
			
		||||
    <string name="unknown_content">[Unknown]</string>
 | 
			
		||||
    <string name="switch_to_background">Switch to Background</string>
 | 
			
		||||
    <string name="switch_to_popup">Switch to Popup</string>
 | 
			
		||||
@@ -243,6 +244,8 @@
 | 
			
		||||
    <string name="restore_defaults_confirmation">Do you want to restore defaults?</string>
 | 
			
		||||
    <string name="permission_display_over_apps">Give permission to display over other apps</string>
 | 
			
		||||
    <!-- error activity -->
 | 
			
		||||
    <string name="error_report_notification_title">NewPipe encountered an error, tap to report</string>
 | 
			
		||||
    <string name="error_report_notification_toast">An error occurred, see the notification</string>
 | 
			
		||||
    <string name="sorry_string">Sorry, that should not have happened.</string>
 | 
			
		||||
    <string name="guru_meditation" translatable="false">Guru Meditation.</string>
 | 
			
		||||
    <string name="error_report_button_text">Report this error via e-mail</string>
 | 
			
		||||
@@ -475,9 +478,11 @@
 | 
			
		||||
    <string name="disable_media_tunneling_summary">Disable media tunneling if you experience a black screen or stuttering on video playback</string>
 | 
			
		||||
    <string name="show_image_indicators_title">Show image indicators</string>
 | 
			
		||||
    <string name="show_image_indicators_summary">Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory</string>
 | 
			
		||||
    <string name="crash_the_app">Crash the app</string>
 | 
			
		||||
    <string name="show_crash_the_player_title">Show \"crash the player\"</string>
 | 
			
		||||
    <string name="show_crash_the_player_summary">Shows a crash option when using the player</string>
 | 
			
		||||
    <string name="crash_the_app">Crash the app</string>
 | 
			
		||||
    <string name="show_error_snackbar">Show an error snackbar</string>
 | 
			
		||||
    <string name="create_error_notification">Create an error notification</string>
 | 
			
		||||
    <!-- Subscriptions import/export -->
 | 
			
		||||
    <string name="import_title">Import</string>
 | 
			
		||||
    <string name="import_from">Import from</string>
 | 
			
		||||
 
 | 
			
		||||
@@ -51,10 +51,9 @@
 | 
			
		||||
 | 
			
		||||
    <SwitchPreferenceCompat
 | 
			
		||||
        android:defaultValue="false"
 | 
			
		||||
        android:key="@string/report_player_errors_key"
 | 
			
		||||
        android:summary="@string/report_player_errors_summary"
 | 
			
		||||
        android:title="@string/report_player_errors_title"
 | 
			
		||||
        app:singleLineTitle="false"
 | 
			
		||||
        android:key="@string/show_crash_the_player_key"
 | 
			
		||||
        android:summary="@string/show_crash_the_player_summary"
 | 
			
		||||
        android:title="@string/show_crash_the_player_title"
 | 
			
		||||
        app:iconSpaceReserved="false" />
 | 
			
		||||
 | 
			
		||||
    <Preference
 | 
			
		||||
@@ -63,12 +62,15 @@
 | 
			
		||||
        app:singleLineTitle="false"
 | 
			
		||||
        app:iconSpaceReserved="false" />
 | 
			
		||||
 | 
			
		||||
    <SwitchPreferenceCompat
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:defaultValue="false"
 | 
			
		||||
        android:key="@string/show_crash_the_player_key"
 | 
			
		||||
        android:summary="@string/show_crash_the_player_summary"
 | 
			
		||||
        android:title="@string/show_crash_the_player_title"
 | 
			
		||||
    <Preference
 | 
			
		||||
        android:key="@string/show_error_snackbar_key"
 | 
			
		||||
        android:title="@string/show_error_snackbar"
 | 
			
		||||
        app:singleLineTitle="false"
 | 
			
		||||
        app:iconSpaceReserved="false" />
 | 
			
		||||
 | 
			
		||||
    <Preference
 | 
			
		||||
        android:key="@string/create_error_notification_key"
 | 
			
		||||
        android:title="@string/create_error_notification"
 | 
			
		||||
        app:singleLineTitle="false"
 | 
			
		||||
        app:iconSpaceReserved="false" />
 | 
			
		||||
</PreferenceScreen>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user