Add report/solve-recaptcha button in error panel

It will be shown even when nothing could be loaded not due to a network error, and the user can choose to ignore or report it.

Also improve error reporting arguments
Also completely refactor error activity
Also improve some code here and there
This commit is contained in:
Stypox 2020-12-11 14:55:47 +01:00
parent bb398189d6
commit 0ea6b4928c
59 changed files with 886 additions and 1262 deletions

View file

@ -202,19 +202,6 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
}
}
@Override
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
showListFooter(false);
if (itemsList != null) {
animate(itemsList, false, 200);
}
if (headerRootBinding != null) {
animate(headerRootBinding.getRoot(), false, 200);
}
}
@Override
public void showEmptyState() {
super.showEmptyState();
@ -249,9 +236,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
}
@Override
protected boolean onError(final Throwable exception) {
public void handleError() {
super.handleError();
resetFragment();
return super.onError(exception);
showListFooter(false);
if (itemsList != null) {
animate(itemsList, false, 200);
}
if (headerRootBinding != null) {
animate(headerRootBinding.getRoot(), false, 200);
}
}
@Override

View file

@ -23,6 +23,7 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
@ -206,7 +207,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
@Override
public void onError(final Throwable exception) {
BookmarkFragment.this.onError(exception);
showError(new ErrorInfo(exception,
UserAction.REQUESTED_BOOKMARK, "Loading playlists"));
}
@Override
@ -237,17 +239,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "Bookmark", R.string.general_error);
return true;
}
@Override
protected void resetFragment() {
super.resetFragment();
@ -295,8 +286,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
.setPositiveButton(R.string.delete, (dialog, i) ->
disposables.add(deleteReactor
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
)
.subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
showError(new ErrorInfo(throwable,
UserAction.REQUESTED_BOOKMARK,
"Deleting playlist")))))
.setNegativeButton(R.string.cancel, null)
.show();
}
@ -314,7 +307,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
localPlaylistManager.renamePlaylist(id, name);
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
.subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
new ErrorInfo(throwable,
UserAction.REQUESTED_BOOKMARK,
"Changing playlist name")));
disposables.add(disposable);
}
}

View file

@ -38,6 +38,7 @@ import icepick.State
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.fragments.list.BaseListFragment
import org.schabi.newpipe.ktx.animate
@ -48,7 +49,6 @@ import java.util.Calendar
class FeedFragment : BaseListFragment<FeedState, Unit>() {
private var _feedBinding: FragmentFeedBinding? = null
private val feedBinding get() = _feedBinding!!
private val errorBinding get() = _feedBinding!!.errorPanel
private lateinit var viewModel: FeedViewModel
@State
@ -171,50 +171,24 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
super.showLoading()
feedBinding.refreshRootView.animate(false, 0)
feedBinding.itemsList.animate(false, 0)
feedBinding.loadingProgressBar.animate(true, 200)
feedBinding.loadingProgressText.animate(true, 200)
feedBinding.emptyStateView.root.animate(false, 0)
errorBinding.root.animate(false, 0)
}
override fun hideLoading() {
super.hideLoading()
feedBinding.refreshRootView.animate(true, 200)
feedBinding.itemsList.animate(true, 300)
feedBinding.loadingProgressBar.animate(false, 0)
feedBinding.loadingProgressText.animate(false, 0)
feedBinding.emptyStateView.root.animate(false, 0)
errorBinding.root.animate(false, 0)
feedBinding.swiperefresh.isRefreshing = false
}
override fun showEmptyState() {
super.showEmptyState()
feedBinding.refreshRootView.animate(true, 200)
feedBinding.itemsList.animate(false, 0)
feedBinding.loadingProgressBar.animate(false, 0)
feedBinding.loadingProgressText.animate(false, 0)
feedBinding.emptyStateView.root.animate(true, 800)
errorBinding.root.animate(false, 0)
}
override fun showError(message: String, showRetryButton: Boolean) {
infoListAdapter.clearStreamItemList()
feedBinding.refreshRootView.animate(false, 120)
feedBinding.itemsList.animate(false, 120)
feedBinding.loadingProgressBar.animate(false, 120)
feedBinding.loadingProgressText.animate(false, 120)
errorBinding.errorMessageView.text = message
errorBinding.errorButtonRetry.animate(showRetryButton, if (showRetryButton) 600 else 0)
errorBinding.root.animate(true, 300)
}
override fun handleResult(result: FeedState) {
@ -227,6 +201,14 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
updateRefreshViewState()
}
override fun handleError() {
super.handleError()
infoListAdapter.clearStreamItemList()
feedBinding.refreshRootView.animate(false, 200)
feedBinding.itemsList.animate(false, 200)
feedBinding.loadingProgressText.animate(false, 200)
}
private fun handleProgressState(progressState: FeedState.ProgressState) {
showLoading()
@ -266,13 +248,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
)
}
if (loadedState.itemsErrors.isNotEmpty()) {
showSnackBarError(
loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error
)
}
if (loadedState.items.isEmpty()) {
showEmptyState()
} else {
@ -281,12 +256,13 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
hideLoading()
errorState.error?.let {
onError(errorState.error)
return true
return if (errorState.error == null) {
hideLoading()
false
} else {
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
true
}
return false
}
private fun updateRelativeTimeViews() {
@ -320,18 +296,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
listState = null
}
override fun onError(exception: Throwable): Boolean {
if (super.onError(exception)) return true
if (useAsFrontPage) {
showSnackBarError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
return true
}
onUnrecoverableError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
return true
}
companion object {
const val KEY_GROUP_ID = "ARG_GROUP_ID"
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"

View file

@ -14,7 +14,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.viewbinding.ViewBinding;
import com.google.android.material.snackbar.Snackbar;
@ -27,6 +26,8 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
@ -34,10 +35,7 @@ import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.settings.HistorySettingsFragment;
import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@ -49,6 +47,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@ -163,48 +162,11 @@ public class StatisticsPlaylistFragment
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_history_clear:
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_view_history_alert)
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
.setPositiveButton(R.string.delete, ((dialog, which) -> {
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> Toast.makeText(getContext(),
R.string.watch_history_deleted,
Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete view history",
R.string.general_error)));
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> {
},
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete search history",
R.string.general_error)));
disposables.add(onClearOrphans);
disposables.add(onDelete);
}))
.create()
.show();
break;
default:
return super.onOptionsItemSelected(item);
if (item.getItemId() == R.id.action_history_clear) {
HistorySettingsFragment
.openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
} else {
return super.onOptionsItemSelected(item);
}
return true;
}
@ -228,7 +190,7 @@ public class StatisticsPlaylistFragment
@Override
public void onPause() {
super.onPause();
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState();
}
@Override
@ -287,7 +249,8 @@ public class StatisticsPlaylistFragment
@Override
public void onError(final Throwable exception) {
StatisticsPlaylistFragment.this.onError(exception);
showError(
new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "History Statistics"));
}
@Override
@ -313,7 +276,7 @@ public class StatisticsPlaylistFragment
}
itemListAdapter.addItems(processResult(result));
if (itemsListState != null) {
if (itemsListState != null && itemsList.getLayoutManager() != null) {
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
itemsListState = null;
}
@ -341,17 +304,6 @@ public class StatisticsPlaylistFragment
}
}
@Override
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "History Statistics", R.string.general_error);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@ -439,9 +391,8 @@ public class StatisticsPlaylistFragment
Toast.LENGTH_SHORT).show();
}
},
throwable -> showSnackBarError(throwable,
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error));
throwable -> showSnackBarError(new ErrorInfo(throwable,
UserAction.DELETE_FROM_HISTORY, "Deleting item")));
disposables.add(onDelete);
}

View file

@ -34,6 +34,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
@ -110,7 +111,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
debouncedSaveSignal = PublishSubject.create();
disposables = new CompositeDisposable();
@ -334,7 +335,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public void onError(final Throwable exception) {
LocalPlaylistFragment.this.onError(exception);
showError(new ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK,
"Loading local playlist"));
}
@Override
@ -344,25 +346,23 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_remove_watched:
if (!isRemovingWatched) {
new AlertDialog.Builder(requireContext())
.setMessage(R.string.remove_watched_popup_warning)
.setTitle(R.string.remove_watched_popup_title)
.setPositiveButton(R.string.yes,
(DialogInterface d, int id) -> removeWatchedStreams(false))
.setNeutralButton(
R.string.remove_watched_popup_yes_and_partially_watched_videos,
(DialogInterface d, int id) -> removeWatchedStreams(true))
.setNegativeButton(R.string.cancel,
(DialogInterface d, int id) -> d.cancel())
.create()
.show();
}
break;
default:
return super.onOptionsItemSelected(item);
if (item.getItemId() == R.id.menu_item_remove_watched) {
if (!isRemovingWatched) {
new AlertDialog.Builder(requireContext())
.setMessage(R.string.remove_watched_popup_warning)
.setTitle(R.string.remove_watched_popup_title)
.setPositiveButton(R.string.yes,
(DialogInterface d, int id) -> removeWatchedStreams(false))
.setNeutralButton(
R.string.remove_watched_popup_yes_and_partially_watched_videos,
(DialogInterface d, int id) -> removeWatchedStreams(true))
.setNegativeButton(R.string.cancel,
(DialogInterface d, int id) -> d.cancel())
.create()
.show();
}
} else {
return super.onOptionsItemSelected(item);
}
return true;
}
@ -455,7 +455,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
hideLoading();
isRemovingWatched = false;
}, this::onError));
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
"Removing watched videos, partially watched=" + removePartiallyWatched))));
}
@Override
@ -511,17 +512,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
}
@Override
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "Local Playlist", R.string.general_error);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist Metadata/Streams Manipulation
//////////////////////////////////////////////////////////////////////////*/
@ -562,7 +552,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
final Disposable disposable = playlistManager.renamePlaylist(playlistId, title)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
.subscribe(longs -> { /*Do nothing on success*/ }, throwable ->
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
"Renaming playlist")));
disposables.add(disposable);
}
@ -583,7 +575,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
final Disposable disposable = playlistManager
.changePlaylistThumbnail(playlistId, thumbnailUrl)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignore -> successToast.show(), this::onError);
.subscribe(ignore -> successToast.show(), throwable ->
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
"Changing playlist thumbnail")));
disposables.add(disposable);
}
@ -632,7 +626,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
return debouncedSaveSignal
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> saveImmediate(), this::onError);
.subscribe(ignored -> saveImmediate(), throwable ->
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
"Debounced saver")));
}
private void saveImmediate() {
@ -669,7 +665,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
isModified.set(false);
}
},
this::onError
throwable -> showError(new ErrorInfo(throwable,
UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
);
disposables.add(disposable);
}
@ -683,7 +680,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
return new ItemTouchHelper.SimpleCallback(directions,
ItemTouchHelper.ACTION_STATE_IDLE) {
@Override
public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView,
public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,
final int viewSize,
final int viewSizeOutOfBounds,
final int totalSize,
@ -696,9 +693,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
@Override
public boolean onMove(final RecyclerView recyclerView,
final RecyclerView.ViewHolder source,
final RecyclerView.ViewHolder target) {
public boolean onMove(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder source,
@NonNull final RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()
|| itemListAdapter == null) {
return false;
@ -724,7 +721,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { }
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int swipeDir) {}
};
}

View file

@ -34,6 +34,7 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.DialogTitleBinding
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.fragments.BaseStateFragment
@ -288,8 +289,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
binding.itemsList.adapter = groupAdapter
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) })
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) })
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) }
}
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
@ -381,7 +382,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
}
is SubscriptionState.ErrorState -> {
result.error?.let { onError(result.error) }
result.error?.let {
showError(ErrorInfo(result.error, UserAction.SOMETHING_ELSE, "Subscriptions"))
}
}
}
}
@ -412,17 +415,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
binding.itemsList.animate(true, 200)
}
// /////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
// /////////////////////////////////////////////////////////////////////////
override fun onError(exception: Throwable): Boolean {
if (super.onError(exception)) return true
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Subscriptions", R.string.general_error)
return true
}
// /////////////////////////////////////////////////////////////////////////
// Grid Mode
// /////////////////////////////////////////////////////////////////////////

View file

@ -84,10 +84,12 @@ public class SubscriptionsImportFragment extends BaseFragment {
setupServiceVariables();
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorActivity.reportError(activity, Collections.emptyList(), null, null,
ErrorInfo.make(UserAction.SOMETHING_ELSE,
ErrorActivity.reportError(activity, null, null,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
NewPipe.getNameOfService(currentServiceId),
"Service don't support importing", R.string.general_error));
"Service does not support importing subscriptions",
R.string.general_error,
null));
activity.finish();
}
}

View file

@ -43,7 +43,6 @@ import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -152,13 +151,10 @@ public abstract class BaseImportExportService extends Service {
postErrorResult(null, null);
}
protected void stopAndReportError(@Nullable final Throwable error, final String request) {
protected void stopAndReportError(final Throwable throwable, final String request) {
stopService();
final ErrorInfo errorInfo = ErrorInfo
.make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error);
ErrorActivity.reportError(this, error != null ? Collections.singletonList(error)
: Collections.emptyList(), null, null, errorInfo);
ErrorActivity.reportError(this, null, null, new ErrorInfo(
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
}
protected void postErrorResult(final String title, final String text) {