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.
This commit is contained in:
parent
3c0a200f7b
commit
4a3e316dd0
|
@ -1,13 +1,11 @@
|
||||||
package org.schabi.newpipe.error;
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context
|
||||||
|
import org.acra.ReportField
|
||||||
import androidx.annotation.NonNull;
|
import org.acra.data.CrashReportData
|
||||||
|
import org.acra.sender.ReportSender
|
||||||
import org.acra.ReportField;
|
import org.schabi.newpipe.R
|
||||||
import org.acra.data.CrashReportData;
|
import org.schabi.newpipe.error.ErrorUtil.Companion.openActivity
|
||||||
import org.acra.sender.ReportSender;
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 13.09.16.
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
class AcraReportSender : ReportSender {
|
||||||
|
override fun send(context: Context, errorContent: CrashReportData) {
|
||||||
|
|
||||||
public class AcraReportSender implements ReportSender {
|
openActivity(
|
||||||
|
context,
|
||||||
@Override
|
ErrorInfo(
|
||||||
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
errorContent.getString(ReportField.STACK_TRACE)?.let { arrayOf(it) }
|
||||||
ErrorUtil.openActivity(context, new ErrorInfo(
|
?: emptyArray(),
|
||||||
new String[]{report.getString(ReportField.STACK_TRACE)},
|
|
||||||
UserAction.UI_ERROR,
|
UserAction.UI_ERROR,
|
||||||
ErrorInfo.SERVICE_NONE,
|
ErrorInfo.SERVICE_NONE,
|
||||||
"ACRA report",
|
"ACRA report",
|
||||||
R.string.app_ui_crash));
|
R.string.app_ui_crash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
package org.schabi.newpipe.error;
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context
|
||||||
|
import com.google.auto.service.AutoService
|
||||||
import androidx.annotation.NonNull;
|
import org.acra.config.CoreConfiguration
|
||||||
|
import org.acra.sender.ReportSender
|
||||||
import com.google.auto.service.AutoService;
|
import org.acra.sender.ReportSenderFactory
|
||||||
|
|
||||||
import org.acra.config.CoreConfiguration;
|
|
||||||
import org.acra.sender.ReportSender;
|
|
||||||
import org.acra.sender.ReportSenderFactory;
|
|
||||||
import org.schabi.newpipe.App;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 13.09.16.
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
@AutoService(ReportSenderFactory::class)
|
||||||
public class AcraReportSenderFactory implements ReportSenderFactory {
|
class AcraReportSenderFactory : ReportSenderFactory {
|
||||||
@NonNull
|
override fun create(
|
||||||
public ReportSender create(@NonNull final Context context,
|
context: Context,
|
||||||
@NonNull final CoreConfiguration config) {
|
config: CoreConfiguration
|
||||||
return new AcraReportSender();
|
): ReportSender {
|
||||||
|
return AcraReportSender()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 <chris.schabesberger@mailbox.org>
|
|
||||||
* 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 <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();
|
|
||||||
// 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<? extends Activity> getReturnActivity(final Class<?> returnActivity) {
|
|
||||||
Class<? extends Activity> 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("<details><summary><b>Exceptions (")
|
|
||||||
.append(errorInfo.getStackTraces().length)
|
|
||||||
.append(")</b></summary><p>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the logs
|
|
||||||
for (int i = 0; i < errorInfo.getStackTraces().length; i++) {
|
|
||||||
htmlErrorReport.append("<details><summary><b>Crash log ");
|
|
||||||
if (errorInfo.getStackTraces().length > 1) {
|
|
||||||
htmlErrorReport.append(i + 1);
|
|
||||||
}
|
|
||||||
htmlErrorReport.append("</b>")
|
|
||||||
.append("</summary><p>\n")
|
|
||||||
.append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n")
|
|
||||||
.append("</details>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure to close everything
|
|
||||||
if (errorInfo.getStackTraces().length > 1) {
|
|
||||||
htmlErrorReport.append("</p></details>\n");
|
|
||||||
}
|
|
||||||
htmlErrorReport.append("<hr>\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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <chris.schabesberger@mailbox.org>
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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>): 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("<details><summary><b>Exceptions (")
|
||||||
|
.append(errorInfo!!.stackTraces.size)
|
||||||
|
.append(")</b></summary><p>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the logs
|
||||||
|
for (i in errorInfo!!.stackTraces.indices) {
|
||||||
|
htmlErrorReport.append("<details><summary><b>Crash log ")
|
||||||
|
if (errorInfo!!.stackTraces.size > 1) {
|
||||||
|
htmlErrorReport.append(i + 1)
|
||||||
|
}
|
||||||
|
htmlErrorReport.append("</b>")
|
||||||
|
.append("</summary><p>\n")
|
||||||
|
.append("\n```\n").append(errorInfo!!.stackTraces[i]).append("\n```\n")
|
||||||
|
.append("</details>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure to close everything
|
||||||
|
if (errorInfo!!.stackTraces.size > 1) {
|
||||||
|
htmlErrorReport.append("</p></details>\n")
|
||||||
|
}
|
||||||
|
htmlErrorReport.append("<hr>\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<out Activity?>? {
|
||||||
|
var checkedReturnActivity: Class<out Activity?>? = null
|
||||||
|
if (returnActivity != null) {
|
||||||
|
checkedReturnActivity = if (Activity::class.java.isAssignableFrom(returnActivity)) {
|
||||||
|
returnActivity.asSubclass(Activity::class.java)
|
||||||
|
} else {
|
||||||
|
MainActivity::class.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkedReturnActivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <bmauduit@beneth.fr> on 06.12.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
|
||||||
* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <bmauduit@beneth.fr> on 06.12.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
||||||
|
}
|
Loading…
Reference in New Issue