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 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
|
@ -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 <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)
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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