From 4a3e316dd04ee5a81f7cf030226d4c9841763f46 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sat, 30 Mar 2024 12:39:07 +0100 Subject: [PATCH] Auto-migrate newpipe/error to kotlin Only error was a nullable check in AcraReportSender, which I fixed by replacing null with an empty array. I tested the error report by producing a network error and opening the report, creating an email and accepting the EULA. Warnings will be fixed in the next commit. --- ...aReportSender.java => AcraReportSender.kt} | 34 +- ...actory.java => AcraReportSenderFactory.kt} | 33 +- .../schabi/newpipe/error/ErrorActivity.java | 348 ----------------- .../org/schabi/newpipe/error/ErrorActivity.kt | 357 ++++++++++++++++++ .../newpipe/error/ReCaptchaActivity.java | 238 ------------ .../schabi/newpipe/error/ReCaptchaActivity.kt | 226 +++++++++++ .../org/schabi/newpipe/error/UserAction.java | 45 --- .../org/schabi/newpipe/error/UserAction.kt | 32 ++ 8 files changed, 647 insertions(+), 666 deletions(-) rename app/src/main/java/org/schabi/newpipe/error/{AcraReportSender.java => AcraReportSender.kt} (60%) rename app/src/main/java/org/schabi/newpipe/error/{AcraReportSenderFactory.java => AcraReportSenderFactory.kt} (55%) delete mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java create mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java create mode 100644 app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/error/UserAction.java create mode 100644 app/src/main/java/org/schabi/newpipe/error/UserAction.kt diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.kt similarity index 60% rename from app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java rename to app/src/main/java/org/schabi/newpipe/error/AcraReportSender.kt index 4d9966364..8bfce5850 100644 --- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.kt @@ -1,13 +1,11 @@ -package org.schabi.newpipe.error; +package org.schabi.newpipe.error -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.acra.ReportField; -import org.acra.data.CrashReportData; -import org.acra.sender.ReportSender; -import org.schabi.newpipe.R; +import android.content.Context +import org.acra.ReportField +import org.acra.data.CrashReportData +import org.acra.sender.ReportSender +import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorUtil.Companion.openActivity /* * Created by Christian Schabesberger on 13.09.16. @@ -28,16 +26,20 @@ import org.schabi.newpipe.R; * You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . */ +class AcraReportSender : ReportSender { + override fun send(context: Context, errorContent: CrashReportData) { -public class AcraReportSender implements ReportSender { - - @Override - public void send(@NonNull final Context context, @NonNull final CrashReportData report) { - ErrorUtil.openActivity(context, new ErrorInfo( - new String[]{report.getString(ReportField.STACK_TRACE)}, + openActivity( + context, + ErrorInfo( + errorContent.getString(ReportField.STACK_TRACE)?.let { arrayOf(it) } + ?: emptyArray(), UserAction.UI_ERROR, ErrorInfo.SERVICE_NONE, "ACRA report", - R.string.app_ui_crash)); + R.string.app_ui_crash + ) + ) + } } diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.kt similarity index 55% rename from app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java rename to app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.kt index e63d55063..7652e1ce0 100644 --- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.kt @@ -1,15 +1,10 @@ -package org.schabi.newpipe.error; +package org.schabi.newpipe.error -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.config.CoreConfiguration; -import org.acra.sender.ReportSender; -import org.acra.sender.ReportSenderFactory; -import org.schabi.newpipe.App; +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.config.CoreConfiguration +import org.acra.sender.ReportSender +import org.acra.sender.ReportSenderFactory /* * Created by Christian Schabesberger on 13.09.16. @@ -30,15 +25,15 @@ import org.schabi.newpipe.App; * You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . */ - /** - * Used by ACRA in {@link App}.initAcra() as the factory for report senders. + * Used by ACRA in [App].initAcra() as the factory for report senders. */ -@AutoService(ReportSenderFactory.class) -public class AcraReportSenderFactory implements ReportSenderFactory { - @NonNull - public ReportSender create(@NonNull final Context context, - @NonNull final CoreConfiguration config) { - return new AcraReportSender(); +@AutoService(ReportSenderFactory::class) +class AcraReportSenderFactory : ReportSenderFactory { + override fun create( + context: Context, + config: CoreConfiguration + ): ReportSender { + return AcraReportSender() } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java deleted file mode 100644 index 831a8cc4b..000000000 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ /dev/null @@ -1,348 +0,0 @@ -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.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.IntentCompat; - -import com.grack.nanojson.JsonWriter; - -import org.schabi.newpipe.BuildConfig; -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.ThemeHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.stream.Collectors; - -/* - * Created by Christian Schabesberger on 24.10.15. - * - * Copyright (C) Christian Schabesberger 2016 - * ErrorActivity.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * < - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * < - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -/** - * 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(); - // BUNDLE TAGS - public static final String ERROR_INFO = "error_info"; - - public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; - public static final String ERROR_EMAIL_SUBJECT = "Exception in "; - - public static final String ERROR_GITHUB_ISSUE_URL = - "https://github.com/TeamNewPipe/NewPipe/issues"; - - public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - - - private ErrorInfo errorInfo; - private String currentTimeStamp; - - private ActivityErrorBinding activityErrorBinding; - - - //////////////////////////////////////////////////////////////////////// - // Activity lifecycle - //////////////////////////////////////////////////////////////////////// - - @Override - protected void onCreate(final Bundle savedInstanceState) { - assureCorrectAppLanguage(this); - super.onCreate(savedInstanceState); - - ThemeHelper.setDayNightMode(this); - ThemeHelper.setTheme(this); - - activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater()); - setContentView(activityErrorBinding.getRoot()); - - final Intent intent = getIntent(); - - setSupportActionBar(activityErrorBinding.toolbarLayout.toolbar); - - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(R.string.error_report_title); - actionBar.setDisplayShowTitleEnabled(true); - } - - errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo.class); - - // important add guru meditation - addGuruMeditation(); - currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now()); - - activityErrorBinding.errorReportEmailButton.setOnClickListener(v -> - openPrivacyPolicyDialog(this, "EMAIL")); - - activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> - ShareUtils.copyToClipboard(this, buildMarkdown())); - - activityErrorBinding.errorReportGitHubButton.setOnClickListener(v -> - openPrivacyPolicyDialog(this, "GITHUB")); - - // normal bugreport - buildInfo(errorInfo); - activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId()); - activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces())); - - // print stack trace once again for debugging: - for (final String e : errorInfo.getStackTraces()) { - Log.e(TAG, e); - } - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - final MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.error_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - case R.id.menu_item_share_error: - ShareUtils.shareText(getApplicationContext(), - getString(R.string.error_report_title), buildJson()); - return true; - default: - return false; - } - } - - private void openPrivacyPolicyDialog(final Context context, final String action) { - new AlertDialog.Builder(context) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.privacy_policy_title) - .setMessage(R.string.start_accept_privacy_policy) - .setCancelable(false) - .setNeutralButton(R.string.read_privacy_policy, (dialog, which) -> - ShareUtils.openUrlInApp(context, - context.getString(R.string.privacy_policy_url))) - .setPositiveButton(R.string.accept, (dialog, which) -> { - if (action.equals("EMAIL")) { // send on email - final Intent i = new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse("mailto:")) // only email apps should handle this - .putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS}) - .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT - + getString(R.string.app_name) + " " - + BuildConfig.VERSION_NAME) - .putExtra(Intent.EXTRA_TEXT, buildJson()); - ShareUtils.openIntentInApp(context, i); - } else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub - ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL); - } - }) - .setNegativeButton(R.string.decline, null) - .show(); - } - - private String formErrorText(final String[] el) { - final String separator = "-------------------------------------"; - return Arrays.stream(el) - .collect(Collectors.joining(separator + "\n", separator + "\n", separator)); - } - - /** - * Get the checked activity. - * - * @param returnActivity the activity to return to - * @return the casted return activity or null - */ - @Nullable - static Class getReturnActivity(final Class returnActivity) { - Class checkedReturnActivity = null; - if (returnActivity != null) { - if (Activity.class.isAssignableFrom(returnActivity)) { - checkedReturnActivity = returnActivity.asSubclass(Activity.class); - } else { - checkedReturnActivity = MainActivity.class; - } - } - return checkedReturnActivity; - } - - private void buildInfo(final ErrorInfo info) { - String text = ""; - - activityErrorBinding.errorInfoLabelsView.setText(getString(R.string.info_labels) - .replace("\\n", "\n")); - - text += getUserActionString(info.getUserAction()) + "\n" - + info.getRequest() + "\n" - + getContentLanguageString() + "\n" - + getContentCountryString() + "\n" - + getAppLanguage() + "\n" - + info.getServiceName() + "\n" - + currentTimeStamp + "\n" - + getPackageName() + "\n" - + BuildConfig.VERSION_NAME + "\n" - + getOsString(); - - activityErrorBinding.errorInfosView.setText(text); - } - - private String buildJson() { - try { - return JsonWriter.string() - .object() - .value("user_action", getUserActionString(errorInfo.getUserAction())) - .value("request", errorInfo.getRequest()) - .value("content_language", getContentLanguageString()) - .value("content_country", getContentCountryString()) - .value("app_language", getAppLanguage()) - .value("service", errorInfo.getServiceName()) - .value("package", getPackageName()) - .value("version", BuildConfig.VERSION_NAME) - .value("os", getOsString()) - .value("time", currentTimeStamp) - .array("exceptions", Arrays.asList(errorInfo.getStackTraces())) - .value("user_comment", activityErrorBinding.errorCommentBox.getText() - .toString()) - .end() - .done(); - } catch (final Throwable e) { - Log.e(TAG, "Error while erroring: Could not build json"); - e.printStackTrace(); - } - - return ""; - } - - private String buildMarkdown() { - try { - final StringBuilder htmlErrorReport = new StringBuilder(); - - final String userComment = activityErrorBinding.errorCommentBox.getText().toString(); - if (!userComment.isEmpty()) { - htmlErrorReport.append(userComment).append("\n"); - } - - // basic error info - htmlErrorReport - .append("## Exception") - .append("\n* __User Action:__ ") - .append(getUserActionString(errorInfo.getUserAction())) - .append("\n* __Request:__ ").append(errorInfo.getRequest()) - .append("\n* __Content Country:__ ").append(getContentCountryString()) - .append("\n* __Content Language:__ ").append(getContentLanguageString()) - .append("\n* __App Language:__ ").append(getAppLanguage()) - .append("\n* __Service:__ ").append(errorInfo.getServiceName()) - .append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME) - .append("\n* __OS:__ ").append(getOsString()).append("\n"); - - - // Collapse all logs to a single paragraph when there are more than one - // to keep the GitHub issue clean. - if (errorInfo.getStackTraces().length > 1) { - htmlErrorReport - .append("
Exceptions (") - .append(errorInfo.getStackTraces().length) - .append(")

\n"); - } - - // add the logs - for (int i = 0; i < errorInfo.getStackTraces().length; i++) { - htmlErrorReport.append("

Crash log "); - if (errorInfo.getStackTraces().length > 1) { - htmlErrorReport.append(i + 1); - } - htmlErrorReport.append("") - .append("

\n") - .append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n") - .append("

\n"); - } - - // make sure to close everything - if (errorInfo.getStackTraces().length > 1) { - htmlErrorReport.append("

\n"); - } - htmlErrorReport.append("
\n"); - return htmlErrorReport.toString(); - } catch (final Throwable e) { - Log.e(TAG, "Error while erroring: Could not build markdown"); - e.printStackTrace(); - return ""; - } - } - - private String getUserActionString(final UserAction userAction) { - if (userAction == null) { - return "Your description is in another castle."; - } else { - return userAction.getMessage(); - } - } - - private String getContentCountryString() { - return Localization.getPreferredContentCountry(this).getCountryCode(); - } - - private String getContentLanguageString() { - return Localization.getPreferredLocalization(this).getLocalizationCode(); - } - - private String getAppLanguage() { - return Localization.getAppLocale(getApplicationContext()).toString(); - } - - private String getOsString() { - final String osBase = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - ? Build.VERSION.BASE_OS : "Android"; - return System.getProperty("os.name") - + " " + (osBase.isEmpty() ? "Android" : osBase) - + " " + Build.VERSION.RELEASE - + " - " + Build.VERSION.SDK_INT; - } - - private void addGuruMeditation() { - //just an easter egg - String text = activityErrorBinding.errorSorryView.getText().toString(); - text += "\n" + getString(R.string.guru_meditation); - activityErrorBinding.errorSorryView.setText(text); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt new file mode 100644 index 000000000..1415ba0b7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt @@ -0,0 +1,357 @@ +package org.schabi.newpipe.error + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.IntentCompat +import com.grack.nanojson.JsonWriter +import org.schabi.newpipe.BuildConfig +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.ThemeHelper +import org.schabi.newpipe.util.external_communication.ShareUtils +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Arrays +import java.util.stream.Collectors + +/* + * Created by Christian Schabesberger on 24.10.15. + * + * Copyright (C) Christian Schabesberger 2016 + * ErrorActivity.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * < + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * < + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +/** + * This activity is used to show error details and allow reporting them in various ways. Use [ ][ErrorUtil.openActivity] to correctly open this activity. + */ +class ErrorActivity : AppCompatActivity() { + private var errorInfo: ErrorInfo? = null + private var currentTimeStamp: String? = null + private var activityErrorBinding: ActivityErrorBinding? = null + + // ////////////////////////////////////////////////////////////////////// + // Activity lifecycle + // ////////////////////////////////////////////////////////////////////// + override fun onCreate(savedInstanceState: Bundle?) { + Localization.assureCorrectAppLanguage(this) + super.onCreate(savedInstanceState) + ThemeHelper.setDayNightMode(this) + ThemeHelper.setTheme(this) + activityErrorBinding = ActivityErrorBinding.inflate( + layoutInflater + ) + setContentView(activityErrorBinding!!.root) + val intent = intent + setSupportActionBar(activityErrorBinding!!.toolbarLayout.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.setTitle(R.string.error_report_title) + actionBar.setDisplayShowTitleEnabled(true) + } + errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo::class.java) + + // important add guru meditation + addGuruMeditation() + currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now()) + activityErrorBinding!!.errorReportEmailButton.setOnClickListener { v: View? -> + openPrivacyPolicyDialog( + this, + "EMAIL" + ) + } + activityErrorBinding!!.errorReportCopyButton.setOnClickListener { v: View? -> + ShareUtils.copyToClipboard( + this, + buildMarkdown() + ) + } + activityErrorBinding!!.errorReportGitHubButton.setOnClickListener { v: View? -> + openPrivacyPolicyDialog( + this, + "GITHUB" + ) + } + + // normal bugreport + buildInfo(errorInfo) + activityErrorBinding!!.errorMessageView.setText(errorInfo!!.messageStringId) + activityErrorBinding!!.errorView.text = formErrorText(errorInfo!!.stackTraces) + + // print stack trace once again for debugging: + for (e in errorInfo!!.stackTraces) { + Log.e(TAG, e) + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater = menuInflater + inflater.inflate(R.menu.error_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + onBackPressed() + true + } + + R.id.menu_item_share_error -> { + ShareUtils.shareText( + applicationContext, + getString(R.string.error_report_title), buildJson() + ) + true + } + + else -> false + } + } + + private fun openPrivacyPolicyDialog(context: Context, action: String) { + AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.privacy_policy_title) + .setMessage(R.string.start_accept_privacy_policy) + .setCancelable(false) + .setNeutralButton(R.string.read_privacy_policy) { dialog: DialogInterface?, which: Int -> + ShareUtils.openUrlInApp( + context, + context.getString(R.string.privacy_policy_url) + ) + } + .setPositiveButton(R.string.accept) { dialog: DialogInterface?, which: Int -> + if (action == "EMAIL") { // send on email + val i = Intent(Intent.ACTION_SENDTO) + .setData(Uri.parse("mailto:")) // only email apps should handle this + .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) + .putExtra( + Intent.EXTRA_SUBJECT, + ERROR_EMAIL_SUBJECT + + getString(R.string.app_name) + " " + + BuildConfig.VERSION_NAME + ) + .putExtra(Intent.EXTRA_TEXT, buildJson()) + ShareUtils.openIntentInApp(context, i) + } else if (action == "GITHUB") { // open the NewPipe issue page on GitHub + ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL) + } + } + .setNegativeButton(R.string.decline, null) + .show() + } + + private fun formErrorText(el: Array): String { + val separator = "-------------------------------------" + return Arrays.stream(el) + .collect( + Collectors.joining( + """ + $separator + + """.trimIndent(), + """ + $separator + + """.trimIndent(), + separator + ) + ) + } + + private fun buildInfo(info: ErrorInfo?) { + var text = "" + activityErrorBinding!!.errorInfoLabelsView.text = getString(R.string.info_labels) + .replace("\\n", "\n") + text += """ + ${getUserActionString(info!!.userAction)} + ${info.request} + $contentLanguageString + $contentCountryString + $appLanguage + ${info.serviceName} + $currentTimeStamp + $packageName + ${BuildConfig.VERSION_NAME} + $osString + """.trimIndent() + activityErrorBinding!!.errorInfosView.text = text + } + + private fun buildJson(): String { + try { + return JsonWriter.string() + .`object`() + .value("user_action", getUserActionString(errorInfo!!.userAction)) + .value("request", errorInfo!!.request) + .value("content_language", contentLanguageString) + .value("content_country", contentCountryString) + .value("app_language", appLanguage) + .value("service", errorInfo!!.serviceName) + .value("package", packageName) + .value("version", BuildConfig.VERSION_NAME) + .value("os", osString) + .value("time", currentTimeStamp) + .array("exceptions", Arrays.asList(*errorInfo!!.stackTraces)) + .value( + "user_comment", + activityErrorBinding!!.errorCommentBox.text + .toString() + ) + .end() + .done() + } catch (e: Throwable) { + Log.e(TAG, "Error while erroring: Could not build json") + e.printStackTrace() + } + return "" + } + + private fun buildMarkdown(): String { + return try { + val htmlErrorReport = StringBuilder() + val userComment = activityErrorBinding!!.errorCommentBox.text.toString() + if (!userComment.isEmpty()) { + htmlErrorReport.append(userComment).append("\n") + } + + // basic error info + htmlErrorReport + .append("## Exception") + .append("\n* __User Action:__ ") + .append(getUserActionString(errorInfo!!.userAction)) + .append("\n* __Request:__ ").append(errorInfo!!.request) + .append("\n* __Content Country:__ ").append(contentCountryString) + .append("\n* __Content Language:__ ").append(contentLanguageString) + .append("\n* __App Language:__ ").append(appLanguage) + .append("\n* __Service:__ ").append(errorInfo!!.serviceName) + .append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME) + .append("\n* __OS:__ ").append(osString).append("\n") + + // Collapse all logs to a single paragraph when there are more than one + // to keep the GitHub issue clean. + if (errorInfo!!.stackTraces.size > 1) { + htmlErrorReport + .append("
Exceptions (") + .append(errorInfo!!.stackTraces.size) + .append(")

\n") + } + + // add the logs + for (i in errorInfo!!.stackTraces.indices) { + htmlErrorReport.append("

Crash log ") + if (errorInfo!!.stackTraces.size > 1) { + htmlErrorReport.append(i + 1) + } + htmlErrorReport.append("") + .append("

\n") + .append("\n```\n").append(errorInfo!!.stackTraces[i]).append("\n```\n") + .append("

\n") + } + + // make sure to close everything + if (errorInfo!!.stackTraces.size > 1) { + htmlErrorReport.append("

\n") + } + htmlErrorReport.append("
\n") + htmlErrorReport.toString() + } catch (e: Throwable) { + Log.e(TAG, "Error while erroring: Could not build markdown") + e.printStackTrace() + "" + } + } + + private fun getUserActionString(userAction: UserAction?): String? { + return if (userAction == null) { + "Your description is in another castle." + } else { + userAction.message + } + } + + private val contentCountryString: String + private get() = Localization.getPreferredContentCountry(this).countryCode + private val contentLanguageString: String + private get() = Localization.getPreferredLocalization(this).localizationCode + private val appLanguage: String + private get() = Localization.getAppLocale(applicationContext).toString() + private val osString: String + private get() { + val osBase = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Build.VERSION.BASE_OS else "Android" + return ( + System.getProperty("os.name") + + " " + (if (osBase.isEmpty()) "Android" else osBase) + + " " + Build.VERSION.RELEASE + + " - " + Build.VERSION.SDK_INT + ) + } + + private fun addGuruMeditation() { + // just an easter egg + var text = activityErrorBinding!!.errorSorryView.text.toString() + text += """ + + ${getString(R.string.guru_meditation)} + """.trimIndent() + activityErrorBinding!!.errorSorryView.text = text + } + + companion object { + // LOG TAGS + val TAG = ErrorActivity::class.java.toString() + + // BUNDLE TAGS + const val ERROR_INFO = "error_info" + const val ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org" + const val ERROR_EMAIL_SUBJECT = "Exception in " + const val ERROR_GITHUB_ISSUE_URL = "https://github.com/TeamNewPipe/NewPipe/issues" + val CURRENT_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + + /** + * Get the checked activity. + * + * @param returnActivity the activity to return to + * @return the casted return activity or null + */ + @JvmStatic + fun getReturnActivity(returnActivity: Class<*>?): Class? { + var checkedReturnActivity: Class? = null + if (returnActivity != null) { + checkedReturnActivity = if (Activity::class.java.isAssignableFrom(returnActivity)) { + returnActivity.asSubclass(Activity::class.java) + } else { + MainActivity::class.java + } + } + return checkedReturnActivity + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java deleted file mode 100644 index 3c14cfe4c..000000000 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ /dev/null @@ -1,238 +0,0 @@ -package org.schabi.newpipe.error; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.webkit.CookieManager; -import android.webkit.WebResourceRequest; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.NavUtils; -import androidx.preference.PreferenceManager; - -import org.schabi.newpipe.DownloaderImpl; -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.databinding.ActivityRecaptchaBinding; -import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.util.ThemeHelper; - -import java.io.UnsupportedEncodingException; - -/* - * Created by beneth on 06.12.16. - * - * Copyright (C) Christian Schabesberger 2015 - * ReCaptchaActivity.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ -public class ReCaptchaActivity extends AppCompatActivity { - public static final int RECAPTCHA_REQUEST = 10; - public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra"; - public static final String TAG = ReCaptchaActivity.class.toString(); - public static final String YT_URL = "https://www.youtube.com"; - public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies"; - - public static String sanitizeRecaptchaUrl(@Nullable final String url) { - if (url == null || url.trim().isEmpty()) { - return YT_URL; // YouTube is the most likely service to have thrown a recaptcha - } else { - // remove "pbj=1" parameter from YouYube urls, as it makes the page JSON and not HTML - return url.replace("&pbj=1", "").replace("pbj=1&", "").replace("?pbj=1", ""); - } - } - - private ActivityRecaptchaBinding recaptchaBinding; - private String foundCookies = ""; - - @SuppressLint("SetJavaScriptEnabled") - @Override - protected void onCreate(final Bundle savedInstanceState) { - ThemeHelper.setTheme(this); - super.onCreate(savedInstanceState); - - recaptchaBinding = ActivityRecaptchaBinding.inflate(getLayoutInflater()); - setContentView(recaptchaBinding.getRoot()); - setSupportActionBar(recaptchaBinding.toolbar); - - final String url = sanitizeRecaptchaUrl(getIntent().getStringExtra(RECAPTCHA_URL_EXTRA)); - // set return to Cancel by default - setResult(RESULT_CANCELED); - - // enable Javascript - final WebSettings webSettings = recaptchaBinding.reCaptchaWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - webSettings.setUserAgentString(DownloaderImpl.USER_AGENT); - - recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(final WebView view, - final WebResourceRequest request) { - if (MainActivity.DEBUG) { - Log.d(TAG, "shouldOverrideUrlLoading: url=" + request.getUrl().toString()); - } - - handleCookiesFromUrl(request.getUrl().toString()); - return false; - } - - @Override - public void onPageFinished(final WebView view, final String url) { - super.onPageFinished(view, url); - handleCookiesFromUrl(url); - } - }); - - // cleaning cache, history and cookies from webView - recaptchaBinding.reCaptchaWebView.clearCache(true); - recaptchaBinding.reCaptchaWebView.clearHistory(); - CookieManager.getInstance().removeAllCookies(null); - - recaptchaBinding.reCaptchaWebView.loadUrl(url); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.menu_recaptcha, menu); - - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setTitle(R.string.title_activity_recaptcha); - actionBar.setSubtitle(R.string.subtitle_activity_recaptcha); - } - - return true; - } - - @Override - public void onBackPressed() { - saveCookiesAndFinish(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == R.id.menu_item_done) { - saveCookiesAndFinish(); - return true; - } - return false; - } - - private void saveCookiesAndFinish() { - // try to get cookies of unclosed page - handleCookiesFromUrl(recaptchaBinding.reCaptchaWebView.getUrl()); - if (MainActivity.DEBUG) { - Log.d(TAG, "saveCookiesAndFinish: foundCookies=" + foundCookies); - } - - if (!foundCookies.isEmpty()) { - // save cookies to preferences - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key); - prefs.edit().putString(key, foundCookies).apply(); - - // give cookies to Downloader class - DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies); - setResult(RESULT_OK); - } - - // Navigate to blank page (unloads youtube to prevent background playback) - recaptchaBinding.reCaptchaWebView.loadUrl("about:blank"); - - final Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - NavUtils.navigateUpTo(this, intent); - } - - - private void handleCookiesFromUrl(@Nullable final String url) { - if (MainActivity.DEBUG) { - Log.d(TAG, "handleCookiesFromUrl: url=" + (url == null ? "null" : url)); - } - - if (url == null) { - return; - } - - final String cookies = CookieManager.getInstance().getCookie(url); - handleCookies(cookies); - - // sometimes cookies are inside the url - final int abuseStart = url.indexOf("google_abuse="); - if (abuseStart != -1) { - final int abuseEnd = url.indexOf("+path"); - - try { - String abuseCookie = url.substring(abuseStart + 13, abuseEnd); - abuseCookie = Utils.decodeUrlUtf8(abuseCookie); - handleCookies(abuseCookie); - } catch (UnsupportedEncodingException | StringIndexOutOfBoundsException e) { - if (MainActivity.DEBUG) { - e.printStackTrace(); - Log.d(TAG, "handleCookiesFromUrl: invalid google abuse starting at " - + abuseStart + " and ending at " + abuseEnd + " for url " + url); - } - } - } - } - - private void handleCookies(@Nullable final String cookies) { - if (MainActivity.DEBUG) { - Log.d(TAG, "handleCookies: cookies=" + (cookies == null ? "null" : cookies)); - } - - if (cookies == null) { - return; - } - - addYoutubeCookies(cookies); - // add here methods to extract cookies for other services - } - - private void addYoutubeCookies(@NonNull final String cookies) { - if (cookies.contains("s_gl=") || cookies.contains("goojf=") - || cookies.contains("VISITOR_INFO1_LIVE=") - || cookies.contains("GOOGLE_ABUSE_EXEMPTION=")) { - // youtube seems to also need the other cookies: - addCookie(cookies); - } - } - - private void addCookie(final String cookie) { - if (foundCookies.contains(cookie)) { - return; - } - - if (foundCookies.isEmpty() || foundCookies.endsWith("; ")) { - foundCookies += cookie; - } else if (foundCookies.endsWith(";")) { - foundCookies += " " + cookie; - } else { - foundCookies += "; " + cookie; - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.kt new file mode 100644 index 000000000..f08808447 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.kt @@ -0,0 +1,226 @@ +package org.schabi.newpipe.error + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NavUtils +import androidx.preference.PreferenceManager +import org.schabi.newpipe.DownloaderImpl +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.ActivityRecaptchaBinding +import org.schabi.newpipe.extractor.utils.Utils +import org.schabi.newpipe.util.ThemeHelper +import java.io.UnsupportedEncodingException + +/* + * Created by beneth on 06.12.16. + * + * Copyright (C) Christian Schabesberger 2015 + * ReCaptchaActivity.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +class ReCaptchaActivity : AppCompatActivity() { + private var recaptchaBinding: ActivityRecaptchaBinding? = null + private var foundCookies = "" + @SuppressLint("SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + ThemeHelper.setTheme(this) + super.onCreate(savedInstanceState) + recaptchaBinding = ActivityRecaptchaBinding.inflate( + layoutInflater + ) + setContentView(recaptchaBinding!!.root) + setSupportActionBar(recaptchaBinding!!.toolbar) + val url = sanitizeRecaptchaUrl(intent.getStringExtra(RECAPTCHA_URL_EXTRA)) + // set return to Cancel by default + setResult(RESULT_CANCELED) + + // enable Javascript + val webSettings = recaptchaBinding!!.reCaptchaWebView.settings + webSettings.javaScriptEnabled = true + webSettings.userAgentString = DownloaderImpl.USER_AGENT + recaptchaBinding!!.reCaptchaWebView.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { + if (MainActivity.DEBUG) { + Log.d(TAG, "shouldOverrideUrlLoading: url=" + request.url.toString()) + } + handleCookiesFromUrl(request.url.toString()) + return false + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + handleCookiesFromUrl(url) + } + } + + // cleaning cache, history and cookies from webView + recaptchaBinding!!.reCaptchaWebView.clearCache(true) + recaptchaBinding!!.reCaptchaWebView.clearHistory() + CookieManager.getInstance().removeAllCookies(null) + recaptchaBinding!!.reCaptchaWebView.loadUrl(url) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_recaptcha, menu) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(false) + actionBar.setTitle(R.string.title_activity_recaptcha) + actionBar.setSubtitle(R.string.subtitle_activity_recaptcha) + } + return true + } + + override fun onBackPressed() { + saveCookiesAndFinish() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.menu_item_done) { + saveCookiesAndFinish() + return true + } + return false + } + + private fun saveCookiesAndFinish() { + // try to get cookies of unclosed page + handleCookiesFromUrl(recaptchaBinding!!.reCaptchaWebView.url) + if (MainActivity.DEBUG) { + Log.d(TAG, "saveCookiesAndFinish: foundCookies=$foundCookies") + } + if (!foundCookies.isEmpty()) { + // save cookies to preferences + val prefs = PreferenceManager.getDefaultSharedPreferences( + applicationContext + ) + val key = applicationContext.getString(R.string.recaptcha_cookies_key) + prefs.edit().putString(key, foundCookies).apply() + + // give cookies to Downloader class + DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies) + setResult(RESULT_OK) + } + + // Navigate to blank page (unloads youtube to prevent background playback) + recaptchaBinding!!.reCaptchaWebView.loadUrl("about:blank") + val intent = Intent(this, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + NavUtils.navigateUpTo(this, intent) + } + + private fun handleCookiesFromUrl(url: String?) { + if (MainActivity.DEBUG) { + Log.d(TAG, "handleCookiesFromUrl: url=" + (url ?: "null")) + } + if (url == null) { + return + } + val cookies = CookieManager.getInstance().getCookie(url) + handleCookies(cookies) + + // sometimes cookies are inside the url + val abuseStart = url.indexOf("google_abuse=") + if (abuseStart != -1) { + val abuseEnd = url.indexOf("+path") + try { + var abuseCookie: String? = url.substring(abuseStart + 13, abuseEnd) + abuseCookie = Utils.decodeUrlUtf8(abuseCookie) + handleCookies(abuseCookie) + } catch (e: UnsupportedEncodingException) { + if (MainActivity.DEBUG) { + e.printStackTrace() + Log.d( + TAG, + "handleCookiesFromUrl: invalid google abuse starting at " + + abuseStart + " and ending at " + abuseEnd + " for url " + url + ) + } + } catch (e: StringIndexOutOfBoundsException) { + if (MainActivity.DEBUG) { + e.printStackTrace() + Log.d( + TAG, + "handleCookiesFromUrl: invalid google abuse starting at " + + abuseStart + " and ending at " + abuseEnd + " for url " + url + ) + } + } + } + } + + private fun handleCookies(cookies: String?) { + if (MainActivity.DEBUG) { + Log.d(TAG, "handleCookies: cookies=" + (cookies ?: "null")) + } + if (cookies == null) { + return + } + addYoutubeCookies(cookies) + // add here methods to extract cookies for other services + } + + private fun addYoutubeCookies(cookies: String) { + if (cookies.contains("s_gl=") || cookies.contains("goojf=") || + cookies.contains("VISITOR_INFO1_LIVE=") || + cookies.contains("GOOGLE_ABUSE_EXEMPTION=") + ) { + // youtube seems to also need the other cookies: + addCookie(cookies) + } + } + + private fun addCookie(cookie: String) { + if (foundCookies.contains(cookie)) { + return + } + foundCookies += if (foundCookies.isEmpty() || foundCookies.endsWith("; ")) { + cookie + } else if (foundCookies.endsWith(";")) { + " $cookie" + } else { + "; $cookie" + } + } + + companion object { + const val RECAPTCHA_REQUEST = 10 + const val RECAPTCHA_URL_EXTRA = "recaptcha_url_extra" + val TAG = ReCaptchaActivity::class.java.toString() + const val YT_URL = "https://www.youtube.com" + const val RECAPTCHA_COOKIES_KEY = "recaptcha_cookies" + fun sanitizeRecaptchaUrl(url: String?): String { + return if (url == null || url.trim { it <= ' ' }.isEmpty()) { + YT_URL // YouTube is the most likely service to have thrown a recaptcha + } else { + // remove "pbj=1" parameter from YouYube urls, as it makes the page JSON and not HTML + url.replace("&pbj=1", "").replace("pbj=1&", "").replace("?pbj=1", "") + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java deleted file mode 100644 index c8701cd77..000000000 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.schabi.newpipe.error; - -/** - * The user actions that can cause an error. - */ -public enum UserAction { - USER_REPORT("user report"), - UI_ERROR("ui error"), - SUBSCRIPTION_CHANGE("subscription change"), - SUBSCRIPTION_UPDATE("subscription update"), - SUBSCRIPTION_GET("get subscription"), - SUBSCRIPTION_IMPORT_EXPORT("subscription import or export"), - LOAD_IMAGE("load image"), - SOMETHING_ELSE("something else"), - SEARCHED("searched"), - GET_SUGGESTIONS("get suggestions"), - REQUESTED_STREAM("requested stream"), - REQUESTED_CHANNEL("requested channel"), - REQUESTED_PLAYLIST("requested playlist"), - REQUESTED_KIOSK("requested kiosk"), - REQUESTED_COMMENTS("requested comments"), - REQUESTED_COMMENT_REPLIES("requested comment replies"), - REQUESTED_FEED("requested feed"), - REQUESTED_BOOKMARK("bookmark"), - DELETE_FROM_HISTORY("delete from history"), - PLAY_STREAM("play stream"), - DOWNLOAD_OPEN_DIALOG("download open dialog"), - DOWNLOAD_POSTPROCESSING("download post-processing"), - DOWNLOAD_FAILED("download failed"), - NEW_STREAMS_NOTIFICATIONS("new streams notifications"), - PREFERENCES_MIGRATION("migration of preferences"), - SHARE_TO_NEWPIPE("share to newpipe"), - CHECK_FOR_NEW_APP_VERSION("check for new app version"), - OPEN_INFO_ITEM_DIALOG("open info item dialog"); - - private final String message; - - UserAction(final String message) { - this.message = message; - } - - public String getMessage() { - return message; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.kt b/app/src/main/java/org/schabi/newpipe/error/UserAction.kt new file mode 100644 index 000000000..193768152 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.kt @@ -0,0 +1,32 @@ +package org.schabi.newpipe.error + +/** + * The user actions that can cause an error. + */ +enum class UserAction(val message: String) { + USER_REPORT("user report"), UI_ERROR("ui error"), SUBSCRIPTION_CHANGE("subscription change"), SUBSCRIPTION_UPDATE( + "subscription update" + ), + SUBSCRIPTION_GET("get subscription"), SUBSCRIPTION_IMPORT_EXPORT("subscription import or export"), LOAD_IMAGE( + "load image" + ), + SOMETHING_ELSE("something else"), SEARCHED("searched"), GET_SUGGESTIONS("get suggestions"), REQUESTED_STREAM( + "requested stream" + ), + REQUESTED_CHANNEL("requested channel"), REQUESTED_PLAYLIST("requested playlist"), REQUESTED_KIOSK( + "requested kiosk" + ), + REQUESTED_COMMENTS("requested comments"), REQUESTED_COMMENT_REPLIES("requested comment replies"), REQUESTED_FEED( + "requested feed" + ), + REQUESTED_BOOKMARK("bookmark"), DELETE_FROM_HISTORY("delete from history"), PLAY_STREAM("play stream"), DOWNLOAD_OPEN_DIALOG( + "download open dialog" + ), + DOWNLOAD_POSTPROCESSING("download post-processing"), DOWNLOAD_FAILED("download failed"), NEW_STREAMS_NOTIFICATIONS( + "new streams notifications" + ), + PREFERENCES_MIGRATION("migration of preferences"), SHARE_TO_NEWPIPE("share to newpipe"), CHECK_FOR_NEW_APP_VERSION( + "check for new app version" + ), + OPEN_INFO_ITEM_DIALOG("open info item dialog") +}