Merge branch 'dev' into pr2335
This commit is contained in:
commit
cd95ec4e12
107 changed files with 1434 additions and 832 deletions
|
|
@ -63,6 +63,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||
return consumed == dy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
|
||||
@NonNull final AppBarLayout child,
|
||||
@NonNull final MotionEvent ev) {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import org.acra.ACRA;
|
|||
import org.acra.config.ACRAConfigurationException;
|
||||
import org.acra.config.CoreConfiguration;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
|
|
@ -217,7 +217,7 @@ public class App extends MultiDexApplication {
|
|||
ACRA.init(this, acraConfig);
|
||||
} catch (final ACRAConfigurationException exception) {
|
||||
exception.printStackTrace();
|
||||
ErrorActivity.reportError(this, new ErrorInfo(exception,
|
||||
ErrorUtil.openActivity(this, new ErrorInfo(exception,
|
||||
UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
|
||||
}
|
||||
}
|
||||
|
|
@ -226,41 +226,44 @@ public class App extends MultiDexApplication {
|
|||
// Keep the importance below DEFAULT to avoid making noise on every notification update for
|
||||
// the main and update channels
|
||||
final NotificationChannelCompat mainChannel = new NotificationChannelCompat
|
||||
.Builder(
|
||||
getString(R.string.notification_channel_id),
|
||||
.Builder(getString(R.string.notification_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(getString(R.string.notification_channel_name))
|
||||
.setDescription(getString(R.string.notification_channel_description))
|
||||
.build();
|
||||
|
||||
final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat
|
||||
.Builder(
|
||||
getString(R.string.app_update_notification_channel_id),
|
||||
.Builder(getString(R.string.app_update_notification_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(getString(R.string.app_update_notification_channel_name))
|
||||
.setDescription(getString(R.string.app_update_notification_channel_description))
|
||||
.build();
|
||||
|
||||
final NotificationChannelCompat hashChannel = new NotificationChannelCompat
|
||||
.Builder(
|
||||
getString(R.string.hash_channel_id),
|
||||
.Builder(getString(R.string.hash_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH)
|
||||
.setName(getString(R.string.hash_channel_name))
|
||||
.setDescription(getString(R.string.hash_channel_description))
|
||||
.build();
|
||||
|
||||
final NotificationChannelCompat errorReportChannel = new NotificationChannelCompat
|
||||
.Builder(getString(R.string.error_report_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(getString(R.string.error_report_channel_name))
|
||||
.setDescription(getString(R.string.error_report_channel_description))
|
||||
.build();
|
||||
|
||||
|
||||
final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat
|
||||
.Builder(
|
||||
getString(R.string.streams_notification_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.Builder(getString(R.string.streams_notification_channel_id),
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(getString(R.string.streams_notification_channel_name))
|
||||
.setDescription(getString(R.string.streams_notification_channel_description))
|
||||
.build();
|
||||
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||
notificationManager.createNotificationChannelsCompat(
|
||||
Arrays.asList(mainChannel, appUpdateChannel, hashChannel, newStreamsChannel)
|
||||
);
|
||||
notificationManager.createNotificationChannelsCompat(Arrays.asList(mainChannel,
|
||||
appUpdateChannel, hashChannel, errorReportChannel, newStreamsChannel));
|
||||
}
|
||||
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import com.grack.nanojson.JsonObject;
|
|||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.downloader.Response;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
|
|
@ -64,7 +64,7 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
|
||||
application.getPackageName());
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
|
||||
return "";
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (final CertificateException e) {
|
||||
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
|
||||
return "";
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
final byte[] publicKey = md.digest(c.getEncoded());
|
||||
return byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding;
|
|||
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
|
||||
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
||||
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
|
|
@ -158,7 +158,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
try {
|
||||
setupDrawer();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Setting up drawer", e);
|
||||
}
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(this);
|
||||
|
|
@ -218,7 +218,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
/**
|
||||
* Builds the drawer menu for the current service.
|
||||
*
|
||||
* @throws ExtractionException
|
||||
* @throws ExtractionException if the service didn't provide available kiosks
|
||||
*/
|
||||
private void addDrawerMenuForCurrentService() throws ExtractionException {
|
||||
//Tabs
|
||||
|
|
@ -270,7 +270,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
try {
|
||||
tabSelected(item);
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e);
|
||||
}
|
||||
break;
|
||||
case R.id.menu_options_about_group:
|
||||
|
|
@ -376,7 +376,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
try {
|
||||
addDrawerMenuForCurrentService();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Showing main page tabs", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -479,7 +479,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
|
||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
|
||||
}
|
||||
|
||||
final SharedPreferences sharedPreferences
|
||||
|
|
@ -789,7 +789,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Handling intent", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
|
|||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
|
@ -231,7 +231,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
|
||||
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
ErrorActivity.reportError(context, errorInfo);
|
||||
ErrorUtil.createNotification(context, errorInfo);
|
||||
}
|
||||
|
||||
if (context instanceof RouterActivity) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE
|
|||
@Dao
|
||||
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||
String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC";
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME
|
||||
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||
|
|
@ -36,16 +37,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
|||
@Override
|
||||
Flowable<List<SearchHistoryEntry>> getAll();
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE
|
||||
+ " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
|
||||
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH
|
||||
+ ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
|
||||
Flowable<List<String>> getUniqueEntries(int limit);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME
|
||||
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
|
||||
+ " GROUP BY " + SEARCH + " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
|
||||
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
|
||||
+ " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
|
||||
Flowable<List<String>> getSimilarEntries(String query, int limit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ import com.nononsenseapps.filepicker.Utils;
|
|||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.DownloadDialogBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
|
|
@ -53,6 +53,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
|
|
@ -402,7 +403,7 @@ public class DownloadDialog extends DialogFragment
|
|||
== R.id.video_button) {
|
||||
setupVideoSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
}, throwable -> ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading video stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
|
|
@ -412,7 +413,7 @@ public class DownloadDialog extends DialogFragment
|
|||
== R.id.audio_button) {
|
||||
setupAudioSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
}, throwable -> ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading audio stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
|
|
@ -422,7 +423,7 @@ public class DownloadDialog extends DialogFragment
|
|||
== R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
}, throwable -> ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading subtitle stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
|
|
@ -687,7 +688,12 @@ public class DownloadDialog extends DialogFragment
|
|||
}
|
||||
|
||||
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
|
||||
launcher.launch(StoredDirectoryHelper.getPicker(context));
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
launcher,
|
||||
StoredDirectoryHelper.getPicker(context),
|
||||
TAG,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
private void prepareSelectedDownload() {
|
||||
|
|
@ -766,8 +772,12 @@ public class DownloadDialog extends DialogFragment
|
|||
initialPath = Uri.parse(initialSavePath.getAbsolutePath());
|
||||
}
|
||||
|
||||
requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context,
|
||||
filenameTmp, mimeTmp, initialPath));
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestDownloadSaveAsLauncher,
|
||||
StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath),
|
||||
TAG,
|
||||
context
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -799,7 +809,7 @@ public class DownloadDialog extends DialogFragment
|
|||
mainStorage.getTag());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportErrorInSnackbar(this,
|
||||
ErrorUtil.createNotification(requireContext(),
|
||||
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,11 @@ public class AcraReportSender implements ReportSender {
|
|||
|
||||
@Override
|
||||
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
||||
ErrorActivity.reportError(context, new ErrorInfo(
|
||||
ErrorUtil.openActivity(context, new ErrorInfo(
|
||||
new String[]{report.getString(ReportField.STACK_TRACE)},
|
||||
UserAction.UI_ERROR,
|
||||
ErrorInfo.SERVICE_NONE,
|
||||
"ACRA report",
|
||||
R.string.app_ui_crash,
|
||||
null));
|
||||
R.string.app_ui_crash));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
package org.schabi.newpipe.error;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ensures that a Exception is serializable.
|
||||
* This is
|
||||
*/
|
||||
public final class EnsureExceptionSerializable {
|
||||
private static final String TAG = "EnsureExSerializable";
|
||||
|
||||
private EnsureExceptionSerializable() {
|
||||
// No instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that an exception is serializable.
|
||||
* <br/>
|
||||
* If that is not the case a {@link WorkaroundNotSerializableException} is created.
|
||||
*
|
||||
* @param exception
|
||||
* @return if an exception is not serializable a new {@link WorkaroundNotSerializableException}
|
||||
* otherwise the exception from the parameter
|
||||
*/
|
||||
public static Exception ensureSerializable(@NonNull final Exception exception) {
|
||||
return checkIfSerializable(exception)
|
||||
? exception
|
||||
: WorkaroundNotSerializableException.create(exception);
|
||||
}
|
||||
|
||||
public static boolean checkIfSerializable(@NonNull final Exception exception) {
|
||||
try {
|
||||
// Check by creating a new ObjectOutputStream which does the serialization
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos)
|
||||
) {
|
||||
oos.writeObject(exception);
|
||||
oos.flush();
|
||||
|
||||
bos.toByteArray();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (final IOException ex) {
|
||||
Log.d(TAG, "Exception is not serializable", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WorkaroundNotSerializableException extends Exception {
|
||||
protected WorkaroundNotSerializableException(
|
||||
final Throwable notSerializableException,
|
||||
final Throwable cause) {
|
||||
super(notSerializableException.toString(), cause);
|
||||
setStackTrace(notSerializableException.getStackTrace());
|
||||
}
|
||||
|
||||
protected WorkaroundNotSerializableException(final Throwable notSerializableException) {
|
||||
super(notSerializableException.toString());
|
||||
setStackTrace(notSerializableException.getStackTrace());
|
||||
}
|
||||
|
||||
public static WorkaroundNotSerializableException create(
|
||||
@NonNull final Exception notSerializableException
|
||||
) {
|
||||
// Build a list of the exception + all causes
|
||||
final List<Throwable> throwableList = new ArrayList<>();
|
||||
|
||||
int pos = 0;
|
||||
Throwable throwableToProcess = notSerializableException;
|
||||
|
||||
while (throwableToProcess != null) {
|
||||
throwableList.add(throwableToProcess);
|
||||
|
||||
pos++;
|
||||
throwableToProcess = throwableToProcess.getCause();
|
||||
}
|
||||
|
||||
// Reverse list so that it starts with the last one
|
||||
Collections.reverse(throwableList);
|
||||
|
||||
// Build exception stack
|
||||
WorkaroundNotSerializableException cause = null;
|
||||
for (final Throwable t : throwableList) {
|
||||
cause = cause == null
|
||||
? new WorkaroundNotSerializableException(t)
|
||||
: new WorkaroundNotSerializableException(t, cause);
|
||||
}
|
||||
|
||||
return cause;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package org.schabi.newpipe.error;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -11,15 +12,12 @@ import android.util.Log;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
|
|
@ -27,15 +25,13 @@ import org.schabi.newpipe.MainActivity;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.ActivityErrorBinding;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 24.10.15.
|
||||
*
|
||||
|
|
@ -56,6 +52,10 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This activity is used to show error details and allow reporting them in various ways. Use {@link
|
||||
* ErrorUtil#openActivity(Context, ErrorInfo)} to correctly open this activity.
|
||||
*/
|
||||
public class ErrorActivity extends AppCompatActivity {
|
||||
// LOG TAGS
|
||||
public static final String TAG = ErrorActivity.class.toString();
|
||||
|
|
@ -77,67 +77,6 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
|
||||
private ActivityErrorBinding activityErrorBinding;
|
||||
|
||||
/**
|
||||
* Reports a new error by starting a new activity.
|
||||
* <br/>
|
||||
* Ensure that the data within errorInfo is serializable otherwise
|
||||
* an exception will be thrown!<br/>
|
||||
* {@link EnsureExceptionSerializable} might help.
|
||||
*
|
||||
* @param context
|
||||
* @param errorInfo
|
||||
*/
|
||||
public static void reportError(final Context context, final ErrorInfo errorInfo) {
|
||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) {
|
||||
final View rootView = context instanceof Activity
|
||||
? ((Activity) context).findViewById(android.R.id.content) : null;
|
||||
reportErrorInSnackbar(context, rootView, errorInfo);
|
||||
}
|
||||
|
||||
public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) {
|
||||
View rootView = fragment.getView();
|
||||
if (rootView == null && fragment.getActivity() != null) {
|
||||
rootView = fragment.getActivity().findViewById(android.R.id.content);
|
||||
}
|
||||
reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo);
|
||||
}
|
||||
|
||||
public static void reportUiErrorInSnackbar(final Context context,
|
||||
final String request,
|
||||
final Throwable throwable) {
|
||||
reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||
}
|
||||
|
||||
public static void reportUiErrorInSnackbar(final Fragment fragment,
|
||||
final String request,
|
||||
final Throwable throwable) {
|
||||
reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static void reportErrorInSnackbar(final Context context,
|
||||
@Nullable final View rootView,
|
||||
final ErrorInfo errorInfo) {
|
||||
if (rootView != null) {
|
||||
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
||||
.setActionTextColor(Color.YELLOW)
|
||||
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
|
||||
reportError(context, errorInfo)).show();
|
||||
} else {
|
||||
reportError(context, errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Activity lifecycle
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package org.schabi.newpipe.error
|
|||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.StringRes
|
||||
import com.google.android.exoplayer2.ExoPlaybackException
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.Info
|
||||
|
|
@ -21,11 +23,14 @@ class ErrorInfo(
|
|||
val userAction: UserAction,
|
||||
val serviceName: String,
|
||||
val request: String,
|
||||
val messageStringId: Int,
|
||||
@Transient // no need to store throwable, all data for report is in other variables
|
||||
var throwable: Throwable? = null
|
||||
val messageStringId: Int
|
||||
) : Parcelable {
|
||||
|
||||
// no need to store throwable, all data for report is in other variables
|
||||
// also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302
|
||||
@IgnoredOnParcel
|
||||
var throwable: Throwable? = null
|
||||
|
||||
private constructor(
|
||||
throwable: Throwable,
|
||||
userAction: UserAction,
|
||||
|
|
@ -36,9 +41,10 @@ class ErrorInfo(
|
|||
userAction,
|
||||
serviceName,
|
||||
request,
|
||||
getMessageStringId(throwable, userAction),
|
||||
throwable
|
||||
)
|
||||
getMessageStringId(throwable, userAction)
|
||||
) {
|
||||
this.throwable = throwable
|
||||
}
|
||||
|
||||
private constructor(
|
||||
throwable: List<Throwable>,
|
||||
|
|
@ -50,9 +56,10 @@ class ErrorInfo(
|
|||
userAction,
|
||||
serviceName,
|
||||
request,
|
||||
getMessageStringId(throwable.firstOrNull(), userAction),
|
||||
throwable.firstOrNull()
|
||||
)
|
||||
getMessageStringId(throwable.firstOrNull(), userAction)
|
||||
) {
|
||||
this.throwable = throwable.firstOrNull()
|
||||
}
|
||||
|
||||
// constructors with single throwable
|
||||
constructor(throwable: Throwable, userAction: UserAction, request: String) :
|
||||
|
|
@ -102,6 +109,13 @@ class ErrorInfo(
|
|||
throwable is ContentNotSupportedException -> R.string.content_not_supported
|
||||
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
|
||||
throwable is ExtractionException -> R.string.parsing_error
|
||||
throwable is ExoPlaybackException -> {
|
||||
when (throwable.type) {
|
||||
ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure
|
||||
ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure
|
||||
else -> R.string.player_unrecoverable_failure
|
||||
}
|
||||
}
|
||||
action == UserAction.UI_ERROR -> R.string.app_ui_crash
|
||||
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
|
||||
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class ErrorPanelHelper(
|
|||
showAndSetErrorButtonAction(
|
||||
R.string.error_snackbar_action
|
||||
) {
|
||||
ErrorActivity.reportError(context, errorInfo)
|
||||
ErrorUtil.openActivity(context, errorInfo)
|
||||
}
|
||||
|
||||
errorTextView.setText(getExceptionDescription(errorInfo.throwable))
|
||||
|
|
@ -178,7 +178,7 @@ class ErrorPanelHelper(
|
|||
val DEBUG: Boolean = MainActivity.DEBUG
|
||||
|
||||
@StringRes
|
||||
public fun getExceptionDescription(throwable: Throwable?): Int {
|
||||
fun getExceptionDescription(throwable: Throwable?): Int {
|
||||
return when (throwable) {
|
||||
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
|
||||
is GeographicRestrictionException -> R.string.georestricted_content
|
||||
|
|
|
|||
165
app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
Normal file
165
app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package org.schabi.newpipe.error
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.schabi.newpipe.R
|
||||
|
||||
/**
|
||||
* This class contains all of the methods that should be used to let the user know that an error has
|
||||
* occurred in the least intrusive way possible for each case. This class is for unexpected errors,
|
||||
* for handled errors (e.g. network errors) use e.g. [ErrorPanelHelper] instead.
|
||||
* - Use a snackbar if the exception is not critical and it happens in a place where a root view
|
||||
* is available.
|
||||
* - Use a notification if the exception happens inside a background service (player, subscription
|
||||
* import, ...) or there is no activity/fragment from which to extract a root view.
|
||||
* - Finally use the error activity only as a last resort in case the exception is critical and
|
||||
* happens in an open activity (since the workflow would be interrupted anyway in that case).
|
||||
*/
|
||||
class ErrorUtil {
|
||||
companion object {
|
||||
private const val ERROR_REPORT_NOTIFICATION_ID = 5340681
|
||||
|
||||
/**
|
||||
* Starts a new error activity allowing the user to report the provided error. Only use this
|
||||
* method directly as a last resort in case the exception is critical and happens in an open
|
||||
* activity (since the workflow would be interrupted anyway in that case). So never use this
|
||||
* for background services.
|
||||
*
|
||||
* @param context the context to use to start the new activity
|
||||
* @param errorInfo the error info to be reported
|
||||
*/
|
||||
@JvmStatic
|
||||
fun openActivity(context: Context, errorInfo: ErrorInfo) {
|
||||
context.startActivity(getErrorActivityIntent(context, errorInfo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a bottom snackbar to the user, with a report button that opens the error activity.
|
||||
* Use this method if the exception is not critical and it happens in a place where a root
|
||||
* view is available.
|
||||
*
|
||||
* @param context will be used to obtain the root view if it is an [Activity]; if no root
|
||||
* view can be found an error notification is shown instead
|
||||
* @param errorInfo the error info to be reported
|
||||
*/
|
||||
@JvmStatic
|
||||
fun showSnackbar(context: Context, errorInfo: ErrorInfo) {
|
||||
val rootView = if (context is Activity) context.findViewById<View>(R.id.content) else null
|
||||
showSnackbar(context, rootView, errorInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a bottom snackbar to the user, with a report button that opens the error activity.
|
||||
* Use this method if the exception is not critical and it happens in a place where a root
|
||||
* view is available.
|
||||
*
|
||||
* @param fragment will be used to obtain the root view if it has a connected [Activity]; if
|
||||
* no root view can be found an error notification is shown instead
|
||||
* @param errorInfo the error info to be reported
|
||||
*/
|
||||
@JvmStatic
|
||||
fun showSnackbar(fragment: Fragment, errorInfo: ErrorInfo) {
|
||||
var rootView = fragment.view
|
||||
if (rootView == null && fragment.activity != null) {
|
||||
rootView = fragment.requireActivity().findViewById(R.id.content)
|
||||
}
|
||||
showSnackbar(fragment.requireContext(), rootView, errorInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun showUiErrorSnackbar(context: Context, request: String, throwable: Throwable) {
|
||||
showSnackbar(context, ErrorInfo(throwable, UserAction.UI_ERROR, request))
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun showUiErrorSnackbar(fragment: Fragment, request: String, throwable: Throwable) {
|
||||
showSnackbar(fragment, ErrorInfo(throwable, UserAction.UI_ERROR, request))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error notification. Tapping on the notification opens the error activity. Use
|
||||
* this method if the exception happens inside a background service (player, subscription
|
||||
* import, ...) or there is no activity/fragment from which to extract a root view.
|
||||
*
|
||||
* @param context the context to use to show the notification
|
||||
* @param errorInfo the error info to be reported; the error message
|
||||
* [ErrorInfo.messageStringId] will be shown in the notification
|
||||
* description
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createNotification(context: Context, errorInfo: ErrorInfo) {
|
||||
val notificationManager =
|
||||
ContextCompat.getSystemService(context, NotificationManager::class.java)
|
||||
if (notificationManager == null) {
|
||||
// this should never happen, but just in case open error activity
|
||||
openActivity(context, errorInfo)
|
||||
}
|
||||
|
||||
var pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE
|
||||
}
|
||||
|
||||
val notificationBuilder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.error_report_channel_id)
|
||||
)
|
||||
.setSmallIcon(R.drawable.ic_bug_report)
|
||||
.setContentTitle(context.getString(R.string.error_report_notification_title))
|
||||
.setContentText(context.getString(errorInfo.messageStringId))
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
getErrorActivityIntent(context, errorInfo),
|
||||
pendingIntentFlags
|
||||
)
|
||||
)
|
||||
|
||||
notificationManager!!.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
|
||||
|
||||
// since the notification is silent, also show a toast, otherwise the user is confused
|
||||
Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent {
|
||||
val intent = Intent(context, ErrorActivity::class.java)
|
||||
intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun showSnackbar(context: Context, rootView: View?, errorInfo: ErrorInfo) {
|
||||
if (rootView == null) {
|
||||
// fallback to showing a notification if no root view is available
|
||||
createNotification(context, errorInfo)
|
||||
} else {
|
||||
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
||||
.setActionTextColor(Color.YELLOW)
|
||||
.setAction(context.getString(R.string.error_snackbar_action).uppercase()) {
|
||||
openActivity(context, errorInfo)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,13 @@ import android.widget.ProgressBar;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorPanelHelper;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
|
@ -198,9 +199,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||
}
|
||||
|
||||
/**
|
||||
* Show a SnackBar and only call
|
||||
* {@link ErrorActivity#reportErrorInSnackbar(androidx.fragment.app.Fragment, ErrorInfo)}
|
||||
* IF we a find a valid view (otherwise the error screen appears).
|
||||
* Directly calls {@link ErrorUtil#showSnackbar(Fragment, ErrorInfo)}, that shows a snackbar if
|
||||
* a valid view can be found, otherwise creates an error report notification.
|
||||
*
|
||||
* @param errorInfo The error information
|
||||
*/
|
||||
|
|
@ -208,6 +208,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
|
||||
}
|
||||
ErrorActivity.reportErrorInSnackbar(this, errorInfo);
|
||||
ErrorUtil.showSnackbar(this, errorInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import com.google.android.material.tabs.TabLayout;
|
|||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.settings.tabs.Tab;
|
||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||
|
|
@ -145,7 +145,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
NavigationHelper.openSearchFragment(getFM(),
|
||||
ServiceHelper.getSelectedServiceId(activity), "");
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Opening search fragment", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Opening search fragment", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -227,16 +227,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
public Fragment getItem(final int position) {
|
||||
final Tab tab = internalTabsList.get(position);
|
||||
|
||||
Throwable throwable = null;
|
||||
Fragment fragment = null;
|
||||
final Fragment fragment;
|
||||
try {
|
||||
fragment = tab.getFragment(context);
|
||||
} catch (final ExtractionException e) {
|
||||
throwable = e;
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable);
|
||||
ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e);
|
||||
return new BlankFragment();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
|
@ -533,7 +533,7 @@ public final class VideoDetailFragment
|
|||
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||
subChannelUrl, subChannelName);
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,7 +685,7 @@ public final class VideoDetailFragment
|
|||
});
|
||||
|
||||
setupBottomPlayer();
|
||||
if (!playerHolder.bound) {
|
||||
if (!playerHolder.isBound()) {
|
||||
setHeightThumbnail();
|
||||
} else {
|
||||
playerHolder.startService(false, this);
|
||||
|
|
@ -1434,7 +1434,7 @@ public final class VideoDetailFragment
|
|||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
// Rebound to the service if it was closed via notification or mini player
|
||||
if (!playerHolder.bound) {
|
||||
if (!playerHolder.isBound()) {
|
||||
playerHolder.startService(
|
||||
false, VideoDetailFragment.this);
|
||||
}
|
||||
|
|
@ -1521,6 +1521,8 @@ public final class VideoDetailFragment
|
|||
animate(binding.detailThumbnailPlayButton, true, 200);
|
||||
binding.detailVideoTitleView.setText(title);
|
||||
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.GONE);
|
||||
|
||||
if (!isEmpty(info.getSubChannelName())) {
|
||||
displayBothUploaderAndSubChannel(info);
|
||||
} else if (!isEmpty(info.getUploaderName())) {
|
||||
|
|
@ -1681,9 +1683,8 @@ public final class VideoDetailFragment
|
|||
|
||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportErrorInSnackbar(activity,
|
||||
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
|
||||
currentInfo));
|
||||
ErrorUtil.showSnackbar(activity, new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Showing download dialog", currentInfo));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1981,7 +1982,9 @@ public final class VideoDetailFragment
|
|||
// Prevent jumping of the player on devices with cutout
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
|
||||
isMultiWindowOrFullscreen()
|
||||
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
: WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
|
||||
}
|
||||
activity.getWindow().getDecorView().setSystemUiVisibility(0);
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
|
@ -2003,7 +2006,9 @@ public final class VideoDetailFragment
|
|||
// Prevent jumping of the player on devices with cutout
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
isMultiWindowOrFullscreen()
|
||||
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
: WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
|
|
@ -2020,7 +2025,7 @@ public final class VideoDetailFragment
|
|||
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
&& (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
|
||||
&& isMultiWindowOrFullscreen()) {
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
|
@ -2036,6 +2041,11 @@ public final class VideoDetailFragment
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isMultiWindowOrFullscreen() {
|
||||
return DeviceUtils.isInMultiWindow(activity)
|
||||
|| (isPlayerAvailable() && player.isFullscreen());
|
||||
}
|
||||
|
||||
private boolean playerIsNotStopped() {
|
||||
return isPlayerAvailable() && !player.isStopped();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import androidx.viewbinding.ViewBinding;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
|
|
@ -293,7 +293,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(
|
||||
ErrorUtil.showUiErrorSnackbar(
|
||||
BaseListFragment.this, "Opening channel fragment", e);
|
||||
}
|
||||
}
|
||||
|
|
@ -309,7 +309,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this,
|
||||
ErrorUtil.showUiErrorSnackbar(BaseListFragment.this,
|
||||
"Opening playlist fragment", e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
|||
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
|
||||
import org.schabi.newpipe.databinding.FragmentChannelBinding;
|
||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
|
|
@ -466,7 +466,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
currentInfo.getParentChannelUrl(),
|
||||
currentInfo.getParentChannelName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
|
||||
}
|
||||
} else if (DEBUG) {
|
||||
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
|
|
@ -310,7 +310,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||
result.getUploaderUrl(), result.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -36,10 +41,9 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.databinding.FragmentSearchBinding;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
|
@ -68,12 +72,11 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
|
|
@ -84,11 +87,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
|
||||
|
||||
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage<?>>
|
||||
implements BackPressable {
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -225,8 +223,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
try {
|
||||
service = NewPipe.getService(serviceId);
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this,
|
||||
"Getting service for id " + serviceId, e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Getting service for id " + serviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -727,7 +724,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (suggestionsPanelVisible
|
||||
&& infoListAdapter.getItemsList().size() > 0
|
||||
&& !infoListAdapter.getItemsList().isEmpty()
|
||||
&& !isLoading.get()) {
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
|
|
@ -743,13 +740,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
return historyRecordManager
|
||||
.getRelatedSearches(query, similarQueryLimit, 25)
|
||||
.toObservable()
|
||||
.map(searchHistoryEntries -> {
|
||||
final Set<SuggestionItem> result = new HashSet<>(); // remove duplicates
|
||||
for (final SearchHistoryEntry entry : searchHistoryEntries) {
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
}
|
||||
return new ArrayList<>(result);
|
||||
});
|
||||
.map(searchHistoryEntries ->
|
||||
searchHistoryEntries.stream()
|
||||
.map(entry -> new SuggestionItem(true, entry))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
|
||||
|
|
|
|||
|
|
@ -34,12 +34,14 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
|||
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
public final TextView itemTitleView;
|
||||
private final ImageView itemHeartView;
|
||||
private final ImageView itemPinnedView;
|
||||
|
||||
public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
||||
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
|
||||
itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -55,5 +57,7 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
|||
itemTitleView.setText(item.getUploaderName());
|
||||
|
||||
itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
|
||||
|
||||
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import android.widget.TextView;
|
|||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
|
|
@ -171,7 +171,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
item.getUploaderUrl(),
|
||||
item.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
|
||||
ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,27 +84,22 @@ class FeedLoadManager(private val context: Context) {
|
|||
|
||||
return outdatedSubscriptions
|
||||
.take(1)
|
||||
|
||||
.doOnNext {
|
||||
currentProgress.set(0)
|
||||
maxProgress.set(it.size)
|
||||
}
|
||||
.filter { it.isNotEmpty() }
|
||||
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext {
|
||||
notificationUpdater.onNext("")
|
||||
broadcastProgress()
|
||||
}
|
||||
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMap { Flowable.fromIterable(it) }
|
||||
.takeWhile { !cancelSignal.get() }
|
||||
|
||||
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
|
||||
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
|
||||
.filter { !cancelSignal.get() }
|
||||
|
||||
.map { subscriptionEntity ->
|
||||
var error: Throwable? = null
|
||||
try {
|
||||
|
|
@ -154,14 +149,11 @@ class FeedLoadManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
.sequential()
|
||||
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext(NotificationConsumer())
|
||||
|
||||
.observeOn(Schedulers.io())
|
||||
.buffer(BUFFER_COUNT_BEFORE_INSERT)
|
||||
.doOnNext(DatabaseConsumer())
|
||||
|
||||
.subscribeOn(Schedulers.io())
|
||||
.toList()
|
||||
.flatMap { x -> postProcessFeed().toSingleDefault(x.flatten()) }
|
||||
|
|
|
|||
|
|
@ -154,7 +154,9 @@ class FeedLoadService : Service() {
|
|||
private fun createNotification(): NotificationCompat.Builder {
|
||||
val cancelActionIntent = PendingIntent.getBroadcast(
|
||||
this,
|
||||
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
|
||||
NOTIFICATION_ID,
|
||||
Intent(ACTION_CANCEL),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
||||
)
|
||||
|
||||
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
|
|
|
|||
|
|
@ -244,9 +244,9 @@ public class HistoryRecordManager {
|
|||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<SearchHistoryEntry>> getRelatedSearches(final String query,
|
||||
final int similarQueryLimit,
|
||||
final int uniqueQueryLimit) {
|
||||
public Flowable<List<String>> getRelatedSearches(final String query,
|
||||
final int similarQueryLimit,
|
||||
final int uniqueQueryLimit) {
|
||||
return query.length() > 0
|
||||
? searchHistoryTable.getSimilarEntries(query, similarQueryLimit)
|
||||
: searchHistoryTable.getUniqueEntries(uniqueQueryLimit);
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
|||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
|
|
@ -179,15 +180,23 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
}
|
||||
|
||||
private fun onImportPreviousSelected() {
|
||||
requestImportLauncher.launch(StoredFileHelper.getPicker(activity, JSON_MIME_TYPE))
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestImportLauncher,
|
||||
StoredFileHelper.getPicker(activity, JSON_MIME_TYPE),
|
||||
TAG,
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
|
||||
private fun onExportSelected() {
|
||||
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
|
||||
val exportName = "newpipe_subscriptions_$date.json"
|
||||
|
||||
requestExportLauncher.launch(
|
||||
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null)
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestExportLauncher,
|
||||
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null),
|
||||
TAG,
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ import androidx.core.text.util.LinkifyCompat;
|
|||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
|
@ -86,12 +87,11 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
|
||||
setupServiceVariables();
|
||||
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
|
||||
ErrorActivity.reportErrorInSnackbar(activity,
|
||||
ErrorUtil.showSnackbar(activity,
|
||||
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
|
||||
NewPipe.getNameOfService(currentServiceId),
|
||||
"Service does not support importing subscriptions",
|
||||
R.string.general_error,
|
||||
null));
|
||||
R.string.general_error));
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
|
@ -175,8 +175,14 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
}
|
||||
|
||||
public void onImportFile() {
|
||||
// leave */* mime type to support all services with different mime types and file extensions
|
||||
requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity, "*/*"));
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestImportFileLauncher,
|
||||
// leave */* mime type to support all services
|
||||
// with different mime types and file extensions
|
||||
StoredFileHelper.getPicker(activity, "*/*"),
|
||||
TAG,
|
||||
getContext()
|
||||
);
|
||||
}
|
||||
|
||||
private void requestImportFileResult(final ActivityResult result) {
|
||||
|
|
|
|||
|
|
@ -54,11 +54,9 @@ class ChannelItem(
|
|||
context.getString(R.string.subscribers_count_not_available)
|
||||
}
|
||||
|
||||
if (itemVersion == ItemVersion.NORMAL) {
|
||||
if (infoItem.streamCount >= 0) {
|
||||
val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount)
|
||||
details = Localization.concatenateStrings(details, formattedVideoAmount)
|
||||
}
|
||||
if (itemVersion == ItemVersion.NORMAL && infoItem.streamCount >= 0) {
|
||||
val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount)
|
||||
details = Localization.concatenateStrings(details, formattedVideoAmount)
|
||||
}
|
||||
return details
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ import androidx.core.app.ServiceCompat;
|
|||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||
|
|
@ -153,7 +153,7 @@ public abstract class BaseImportExportService extends Service {
|
|||
|
||||
protected void stopAndReportError(final Throwable throwable, final String request) {
|
||||
stopService();
|
||||
ErrorActivity.reportError(this, new ErrorInfo(
|
||||
ErrorUtil.createNotification(this, new ErrorInfo(
|
||||
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,9 @@ import org.schabi.newpipe.MainActivity;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.PlayerBinding;
|
||||
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||
|
|
@ -165,7 +168,6 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
|
|||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playback.PlayerMediaSession;
|
||||
import org.schabi.newpipe.player.playback.SurfaceHolderCallback;
|
||||
import org.schabi.newpipe.player.playererror.PlayerErrorHandler;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
|
@ -268,8 +270,6 @@ public final class Player implements
|
|||
@Nullable private MediaSourceTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
|
||||
@NonNull private PlayerErrorHandler playerErrorHandler;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -413,8 +413,6 @@ public final class Player implements
|
|||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||
|
||||
playerErrorHandler = new PlayerErrorHandler(context);
|
||||
|
||||
windowManager = ContextCompat.getSystemService(context, WindowManager.class);
|
||||
}
|
||||
|
||||
|
|
@ -2350,7 +2348,8 @@ public final class Player implements
|
|||
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
|
||||
}
|
||||
|
||||
private void setRepeatModeButton(final AppCompatImageButton imageButton, final int repeatMode) {
|
||||
private void setRepeatModeButton(final AppCompatImageButton imageButton,
|
||||
@RepeatMode final int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case REPEAT_MODE_OFF:
|
||||
imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
|
||||
|
|
@ -2364,7 +2363,7 @@ public final class Player implements
|
|||
}
|
||||
}
|
||||
|
||||
private void setShuffleButton(final ImageButton button, final boolean shuffled) {
|
||||
private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) {
|
||||
button.setImageAlpha(shuffled ? 255 : 77);
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -2389,7 +2388,7 @@ public final class Player implements
|
|||
return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0;
|
||||
}
|
||||
|
||||
private void setMuteButton(final ImageButton button, final boolean isMuted) {
|
||||
private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) {
|
||||
button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted
|
||||
? R.drawable.ic_volume_off : R.drawable.ic_volume_up));
|
||||
}
|
||||
|
|
@ -2518,29 +2517,30 @@ public final class Player implements
|
|||
|
||||
saveStreamProgressState();
|
||||
|
||||
// create error notification
|
||||
final ErrorInfo errorInfo;
|
||||
if (currentMetadata == null) {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred, currentMetadata is null");
|
||||
} else {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred while playing "
|
||||
+ currentMetadata.getMetadata().getUrl(),
|
||||
currentMetadata.getMetadata());
|
||||
}
|
||||
ErrorUtil.createNotification(context, errorInfo);
|
||||
|
||||
switch (error.type) {
|
||||
case ExoPlaybackException.TYPE_SOURCE:
|
||||
processSourceError(error.getSourceException());
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_stream_failure);
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_UNEXPECTED:
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_recoverable_failure);
|
||||
setRecovery();
|
||||
reloadPlayQueueManager();
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_REMOTE:
|
||||
case ExoPlaybackException.TYPE_RENDERER:
|
||||
default:
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_unrecoverable_failure);
|
||||
onPlaybackShutdown();
|
||||
break;
|
||||
}
|
||||
|
|
@ -2877,7 +2877,7 @@ public final class Player implements
|
|||
databaseUpdateDisposable
|
||||
.add(recordManager.saveStreamState(currentMetadata.getMetadata(), progressMillis)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnError((e) -> {
|
||||
.doOnError(e -> {
|
||||
if (DEBUG) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -3387,7 +3387,7 @@ public final class Player implements
|
|||
playbackSpeedPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
|
||||
private void buildCaptionMenu(final List<String> availableLanguages) {
|
||||
private void buildCaptionMenu(@NonNull final List<String> availableLanguages) {
|
||||
if (captionPopupMenu == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3455,7 +3455,7 @@ public final class Player implements
|
|||
* Called when an item of the quality selector or the playback speed selector is selected.
|
||||
*/
|
||||
@Override
|
||||
public boolean onMenuItemClick(final MenuItem menuItem) {
|
||||
public boolean onMenuItemClick(@NonNull final MenuItem menuItem) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onMenuItemClick() called with: "
|
||||
+ "menuItem = [" + menuItem + "], "
|
||||
|
|
@ -3492,7 +3492,7 @@ public final class Player implements
|
|||
* Called when some popup menu is dismissed.
|
||||
*/
|
||||
@Override
|
||||
public void onDismiss(final PopupMenu menu) {
|
||||
public void onDismiss(@Nullable final PopupMenu menu) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
|
||||
}
|
||||
|
|
@ -3545,7 +3545,7 @@ public final class Player implements
|
|||
isSomePopupMenuVisible = true;
|
||||
}
|
||||
|
||||
private void setPlaybackQuality(final String quality) {
|
||||
private void setPlaybackQuality(@Nullable final String quality) {
|
||||
videoResolver.setPlaybackQuality(quality);
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -3569,7 +3569,7 @@ public final class Player implements
|
|||
final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels);
|
||||
final float captionRatioInverse = 20f + 4f * (1.0f - captionScale);
|
||||
binding.subtitleView.setFixedTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX, (float) minimumLength / captionRatioInverse);
|
||||
TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse);
|
||||
}
|
||||
binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
|
||||
binding.subtitleView.setStyle(captionStyle);
|
||||
|
|
@ -3846,7 +3846,7 @@ public final class Player implements
|
|||
}
|
||||
|
||||
@Override // exoplayer listener
|
||||
public void onVideoSizeChanged(final VideoSize videoSize) {
|
||||
public void onVideoSizeChanged(@NonNull final VideoSize videoSize) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onVideoSizeChanged() called with: "
|
||||
+ "width / height = [" + videoSize.width + " / " + videoSize.height
|
||||
|
|
@ -3960,7 +3960,7 @@ public final class Player implements
|
|||
}
|
||||
}
|
||||
|
||||
private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
|
||||
private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) {
|
||||
final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft()
|
||||
+ closeOverlayBinding.closeButton.getWidth() / 2;
|
||||
final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop()
|
||||
|
|
@ -3979,7 +3979,7 @@ public final class Player implements
|
|||
return buttonRadius * 1.2f;
|
||||
}
|
||||
|
||||
public boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) {
|
||||
public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) {
|
||||
return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -4099,6 +4099,7 @@ public final class Player implements
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AppCompatActivity getParentActivity() {
|
||||
// ! instanceof ViewGroup means that view was added via windowManager for Popup
|
||||
if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
package org.schabi.newpipe.player.event;
|
||||
|
||||
import static org.schabi.newpipe.ktx.AnimationType.ALPHA;
|
||||
import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION;
|
||||
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
|
@ -8,22 +15,15 @@ import android.view.Window;
|
|||
import android.view.WindowManager;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.player.MainPlayer;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
|
||||
import static org.schabi.newpipe.ktx.AnimationType.ALPHA;
|
||||
import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION;
|
||||
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
|
||||
|
||||
/**
|
||||
* GestureListener for the player
|
||||
*
|
||||
|
|
@ -45,8 +45,8 @@ public class PlayerGestureListener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDoubleTap(@NotNull final MotionEvent event,
|
||||
@NotNull final DisplayPortion portion) {
|
||||
public void onDoubleTap(@NonNull final MotionEvent event,
|
||||
@NonNull final DisplayPortion portion) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDoubleTap called with playerType = ["
|
||||
+ player.getPlayerType() + "], portion = [" + portion + "]");
|
||||
|
|
@ -65,7 +65,7 @@ public class PlayerGestureListener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) {
|
||||
public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]");
|
||||
}
|
||||
|
|
@ -85,10 +85,10 @@ public class PlayerGestureListener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(@NotNull final MainPlayer.PlayerType playerType,
|
||||
@NotNull final DisplayPortion portion,
|
||||
@NotNull final MotionEvent initialEvent,
|
||||
@NotNull final MotionEvent movingEvent,
|
||||
public void onScroll(@NonNull final MainPlayer.PlayerType playerType,
|
||||
@NonNull final DisplayPortion portion,
|
||||
@NonNull final MotionEvent initialEvent,
|
||||
@NonNull final MotionEvent movingEvent,
|
||||
final float distanceX, final float distanceY) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScroll called with playerType = ["
|
||||
|
|
@ -197,8 +197,8 @@ public class PlayerGestureListener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd(@NotNull final MainPlayer.PlayerType playerType,
|
||||
@NotNull final MotionEvent event) {
|
||||
public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType,
|
||||
@NonNull final MotionEvent event) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScrollEnd called with playerType = ["
|
||||
+ player.getPlayerType() + "]");
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
|
||||
import com.google.android.exoplayer2.source.chunk.MediaParserChunkExtractor;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.MediaParserHlsMediaChunkExtractor;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
|
|
@ -46,17 +42,10 @@ public class PlayerDataSource {
|
|||
}
|
||||
|
||||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
final HlsMediaSource.Factory factory =
|
||||
new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
|
||||
}
|
||||
|
||||
return factory;
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
|
|
@ -71,26 +60,11 @@ public class PlayerDataSource {
|
|||
private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
|
||||
final DataSource.Factory dataSourceFactory
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return new DefaultDashChunkSource.Factory(
|
||||
MediaParserChunkExtractor.FACTORY,
|
||||
dataSourceFactory,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
return new DefaultDashChunkSource.Factory(dataSourceFactory);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getHlsMediaSourceFactory() {
|
||||
final HlsMediaSource.Factory factory = new HlsMediaSource.Factory(cacheDataSourceFactory);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return factory;
|
||||
}
|
||||
|
||||
// *** >= Android 11 / R / API 30 ***
|
||||
return factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
|
||||
return new HlsMediaSource.Factory(cacheDataSourceFactory);
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getDashMediaSourceFactory() {
|
||||
|
|
@ -101,18 +75,9 @@ public class PlayerDataSource {
|
|||
}
|
||||
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
|
||||
final ProgressiveMediaSource.Factory factory;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
factory = new ProgressiveMediaSource.Factory(
|
||||
cacheDataSourceFactory,
|
||||
MediaParserExtractorAdapter.FACTORY
|
||||
);
|
||||
} else {
|
||||
factory = new ProgressiveMediaSource.Factory(cacheDataSourceFactory);
|
||||
}
|
||||
|
||||
return factory.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() {
|
||||
|
|
|
|||
|
|
@ -35,13 +35,13 @@ public final class PlayerHolder {
|
|||
return PlayerHolder.instance;
|
||||
}
|
||||
|
||||
private final boolean DEBUG = MainActivity.DEBUG;
|
||||
private final String TAG = PlayerHolder.class.getSimpleName();
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final String TAG = PlayerHolder.class.getSimpleName();
|
||||
|
||||
private PlayerServiceExtendedEventListener listener;
|
||||
|
||||
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
|
||||
public boolean bound;
|
||||
private boolean bound;
|
||||
private MainPlayer playerService;
|
||||
private Player player;
|
||||
|
||||
|
|
@ -70,6 +70,10 @@ public final class PlayerHolder {
|
|||
return player != null;
|
||||
}
|
||||
|
||||
public boolean isBound() {
|
||||
return bound;
|
||||
}
|
||||
|
||||
public int getQueueSize() {
|
||||
return isPlayerOpen() ? player.getPlayQueue().size() : 0;
|
||||
}
|
||||
|
|
@ -148,7 +152,7 @@ public final class PlayerHolder {
|
|||
}
|
||||
startPlayerListener();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void bind(final Context context) {
|
||||
if (DEBUG) {
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
package org.schabi.newpipe.player.playererror;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.EnsureExceptionSerializable;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
||||
/**
|
||||
* Handles (exoplayer)errors that occur in the player.
|
||||
*/
|
||||
public class PlayerErrorHandler {
|
||||
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
|
||||
// or it fails with an IllegalArgumentException
|
||||
// https://stackoverflow.com/a/54744028
|
||||
private static final String TAG = "PlayerErrorHandler";
|
||||
|
||||
@Nullable
|
||||
private Toast errorToast;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
|
||||
public PlayerErrorHandler(@NonNull final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void showPlayerError(
|
||||
@NonNull final ExoPlaybackException exception,
|
||||
@NonNull final Info info,
|
||||
@StringRes final int textResId
|
||||
) {
|
||||
// Hide existing toast message
|
||||
if (errorToast != null) {
|
||||
Log.d(TAG, "Trying to cancel previous player error error toast");
|
||||
errorToast.cancel();
|
||||
errorToast = null;
|
||||
}
|
||||
|
||||
if (shouldReportError()) {
|
||||
try {
|
||||
reportError(exception, info);
|
||||
// When a report pops up we need no toast
|
||||
return;
|
||||
} catch (final Exception ex) {
|
||||
Log.w(TAG, "Unable to report error:", ex);
|
||||
// This will show the toast as fallback
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Showing player error toast");
|
||||
errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT);
|
||||
errorToast.show();
|
||||
}
|
||||
|
||||
private void reportError(@NonNull final ExoPlaybackException exception,
|
||||
@NonNull final Info info) {
|
||||
ErrorActivity.reportError(
|
||||
context,
|
||||
new ErrorInfo(
|
||||
EnsureExceptionSerializable.ensureSerializable(exception),
|
||||
UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + exception.type + "] occurred while playing: "
|
||||
+ info.getUrl(),
|
||||
info
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean shouldReportError() {
|
||||
return PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getBoolean(
|
||||
context.getString(R.string.report_player_errors_key),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ import java.util.Objects;
|
|||
|
||||
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||
protected final boolean DEBUG = MainActivity.DEBUG;
|
||||
protected static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
SharedPreferences defaultPreferences;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ import androidx.preference.PreferenceManager;
|
|||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PicassoHelper;
|
||||
|
|
@ -73,19 +74,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||
|
||||
final Preference importDataPreference = requirePreference(R.string.import_data);
|
||||
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
requestImportPathLauncher.launch(
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestImportPathLauncher,
|
||||
StoredFileHelper.getPicker(requireContext(),
|
||||
ZIP_MIME_TYPE, getImportExportDataUri()));
|
||||
ZIP_MIME_TYPE, getImportExportDataUri()),
|
||||
TAG,
|
||||
getContext()
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference exportDataPreference = requirePreference(R.string.export_data);
|
||||
exportDataPreference.setOnPreferenceClickListener((final Preference p) -> {
|
||||
|
||||
requestExportPathLauncher.launch(
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestExportPathLauncher,
|
||||
StoredFileHelper.getNewPicker(requireContext(),
|
||||
"NewPipeData-" + exportDateFormat.format(new Date()) + ".zip",
|
||||
ZIP_MIME_TYPE, getImportExportDataUri()));
|
||||
ZIP_MIME_TYPE, getImportExportDataUri()),
|
||||
TAG,
|
||||
getContext()
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
@ -205,7 +215,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||
saveLastImportExportDataUri(exportDataUri); // save export path only on success
|
||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +257,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||
finishImport(importDataUri);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat;
|
|||
import com.nononsenseapps.filepicker.Utils;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
|
||||
|
|
@ -214,7 +215,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
|||
}
|
||||
|
||||
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
|
||||
launcher.launch(StoredDirectoryHelper.getPicker(ctx));
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
launcher,
|
||||
StoredDirectoryHelper.getPicker(ctx),
|
||||
TAG,
|
||||
ctx
|
||||
);
|
||||
}
|
||||
|
||||
private void requestDownloadVideoPathResult(final ActivityResult result) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import androidx.appcompat.app.AlertDialog;
|
|||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
|
|
@ -64,7 +64,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(context,
|
||||
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(context,
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete playback states")));
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(context,
|
||||
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(context,
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete from history")));
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> { },
|
||||
throwable -> ErrorActivity.reportError(context,
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Clear orphaned records")));
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(context,
|
||||
R.string.search_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(context,
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete search history")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import io.reactivex.rxjava3.disposables.Disposable
|
|||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.subscription.NotificationMode
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.error.ErrorActivity
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
import org.schabi.newpipe.error.ErrorUtil
|
||||
import org.schabi.newpipe.error.UserAction
|
||||
import org.schabi.newpipe.local.feed.notifications.NotificationHelper
|
||||
import org.schabi.newpipe.local.feed.notifications.NotificationWorker
|
||||
|
|
@ -118,7 +118,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen
|
|||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
ErrorActivity.reportErrorInSnackbar(
|
||||
ErrorUtil.showSnackbar(
|
||||
this,
|
||||
ErrorInfo(e, UserAction.SUBSCRIPTION_GET, "Get subscriptions list")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||
import org.schabi.newpipe.util.PicassoHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
|
@ -153,7 +153,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||
|
||||
@Override
|
||||
public void onError(@NonNull final Throwable exception) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
|
||||
ErrorUtil.showUiErrorSnackbar(SelectChannelFragment.this,
|
||||
"Loading subscription", exception);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
|
|
@ -48,7 +48,6 @@ import java.util.Vector;
|
|||
*/
|
||||
|
||||
public class SelectKioskFragment extends DialogFragment {
|
||||
private RecyclerView recyclerView = null;
|
||||
private SelectKioskAdapter selectKioskAdapter = null;
|
||||
|
||||
private OnSelectedListener onSelectedListener = null;
|
||||
|
|
@ -76,12 +75,12 @@ public class SelectKioskFragment extends DialogFragment {
|
|||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
|
||||
recyclerView = v.findViewById(R.id.items_list);
|
||||
final RecyclerView recyclerView = v.findViewById(R.id.items_list);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
try {
|
||||
selectKioskAdapter = new SelectKioskAdapter();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e);
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Selecting kiosk", e);
|
||||
}
|
||||
recyclerView.setAdapter(selectKioskAdapter);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
|
@ -21,8 +20,8 @@ import org.schabi.newpipe.database.LocalItem;
|
|||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
|
|
@ -105,8 +104,7 @@ public class SelectPlaylistFragment extends DialogFragment {
|
|||
}
|
||||
|
||||
protected void onError(final Throwable e) {
|
||||
final Activity activity = requireActivity();
|
||||
ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e,
|
||||
ErrorUtil.showSnackbar(requireActivity(), new ErrorInfo(e,
|
||||
UserAction.UI_ERROR, "Loading playlists"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import android.util.Log;
|
|||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ public final class SettingMigrations {
|
|||
} catch (final Exception e) {
|
||||
// save the version with the last successful migration and report the error
|
||||
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
|
||||
ErrorActivity.reportError(context, new ErrorInfo(
|
||||
ErrorUtil.openActivity(context, new ErrorInfo(
|
||||
e,
|
||||
UserAction.PREFERENCES_MIGRATION,
|
||||
"Migrating preferences from version " + lastPrefVersion + " to "
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||
|
|
@ -182,7 +182,7 @@ public class ChooseTabsFragment extends Fragment {
|
|||
final Tab.Type type = typeFrom(tabId);
|
||||
|
||||
if (type == null) {
|
||||
ErrorActivity.reportErrorInSnackbar(this,
|
||||
ErrorUtil.showSnackbar(this,
|
||||
new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
|
||||
UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import com.grack.nanojson.JsonSink;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
|
@ -36,6 +36,10 @@ import java.util.Objects;
|
|||
public abstract class Tab {
|
||||
private static final String JSON_TAB_ID_KEY = "tab_id";
|
||||
|
||||
private static final String NO_NAME = "<no-name>";
|
||||
private static final String NO_ID = "<no-id>";
|
||||
private static final String NO_URL = "<no-url>";
|
||||
|
||||
Tab() {
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +189,9 @@ public abstract class Tab {
|
|||
|
||||
@Override
|
||||
public String getTabName(final Context context) {
|
||||
return "NewPipe"; //context.getString(R.string.blank_page_summary);
|
||||
// TODO: find a better name for the blank tab (maybe "blank_tab") or replace it with
|
||||
// context.getString(R.string.app_name);
|
||||
return "NewPipe"; // context.getString(R.string.blank_page_summary);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
|
|
@ -309,7 +315,7 @@ public abstract class Tab {
|
|||
private String kioskId;
|
||||
|
||||
private KioskTab() {
|
||||
this(-1, "<no-id>");
|
||||
this(-1, NO_ID);
|
||||
}
|
||||
|
||||
public KioskTab(final int kioskServiceId, final String kioskId) {
|
||||
|
|
@ -357,7 +363,7 @@ public abstract class Tab {
|
|||
@Override
|
||||
protected void readDataFromJson(final JsonObject jsonObject) {
|
||||
kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1);
|
||||
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, "<no-id>");
|
||||
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, NO_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -395,7 +401,7 @@ public abstract class Tab {
|
|||
private String channelName;
|
||||
|
||||
private ChannelTab() {
|
||||
this(-1, "<no-url>", "<no-name>");
|
||||
this(-1, NO_URL, NO_NAME);
|
||||
}
|
||||
|
||||
public ChannelTab(final int channelServiceId, final String channelUrl,
|
||||
|
|
@ -440,8 +446,8 @@ public abstract class Tab {
|
|||
@Override
|
||||
protected void readDataFromJson(final JsonObject jsonObject) {
|
||||
channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1);
|
||||
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, "<no-url>");
|
||||
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, "<no-name>");
|
||||
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, NO_URL);
|
||||
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, NO_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -506,7 +512,7 @@ public abstract class Tab {
|
|||
final StreamingService service = NewPipe.getService(kioskServiceId);
|
||||
kioskId = service.getKioskList().getDefaultKioskId();
|
||||
} catch (final ExtractionException e) {
|
||||
ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e,
|
||||
ErrorUtil.showSnackbar(context, new ErrorInfo(e,
|
||||
UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
|
||||
}
|
||||
return kioskId;
|
||||
|
|
@ -527,7 +533,7 @@ public abstract class Tab {
|
|||
private LocalItemType playlistType;
|
||||
|
||||
private PlaylistTab() {
|
||||
this(-1, "<no-name>");
|
||||
this(-1, NO_NAME);
|
||||
}
|
||||
|
||||
public PlaylistTab(final long playlistId, final String playlistName) {
|
||||
|
|
@ -535,7 +541,7 @@ public abstract class Tab {
|
|||
this.playlistId = playlistId;
|
||||
this.playlistType = LocalItemType.PLAYLIST_LOCAL_ITEM;
|
||||
this.playlistServiceId = -1;
|
||||
this.playlistUrl = "<no-url>";
|
||||
this.playlistUrl = NO_URL;
|
||||
}
|
||||
|
||||
public PlaylistTab(final int playlistServiceId, final String playlistUrl,
|
||||
|
|
@ -589,8 +595,8 @@ public abstract class Tab {
|
|||
@Override
|
||||
protected void readDataFromJson(final JsonObject jsonObject) {
|
||||
playlistServiceId = jsonObject.getInt(JSON_PLAYLIST_SERVICE_ID_KEY, -1);
|
||||
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, "<no-url>");
|
||||
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, "<no-name>");
|
||||
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, NO_URL);
|
||||
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, NO_NAME);
|
||||
playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1);
|
||||
playlistType = LocalItemType.valueOf(
|
||||
jsonObject.getString(JSON_PLAYLIST_TYPE_KEY,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package org.schabi.newpipe.streams.io;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
/**
|
||||
* Helper for when no file-manager/activity was found.
|
||||
*/
|
||||
public final class NoFileManagerSafeGuard {
|
||||
private NoFileManagerSafeGuard() {
|
||||
// No impl
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an alert dialog when no file-manager is found.
|
||||
* @param context Context
|
||||
*/
|
||||
private static void showActivityNotFoundAlert(final Context context) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to open no file manager alert dialog: Context is null");
|
||||
}
|
||||
|
||||
final String message;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10+ only allows SAF
|
||||
message = context.getString(R.string.no_appropriate_file_manager_message_android_10);
|
||||
} else {
|
||||
message = context.getString(
|
||||
R.string.no_appropriate_file_manager_message,
|
||||
context.getString(R.string.downloads_storage_use_saf_title));
|
||||
}
|
||||
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.no_app_to_open_intent)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the file manager safely.
|
||||
*
|
||||
* If no file manager is found (which is normally only the case when the user uninstalled
|
||||
* the default file manager or the OS lacks one) an alert dialog shows up, asking the user
|
||||
* to fix the situation.
|
||||
*
|
||||
* @param activityResultLauncher see {@link ActivityResultLauncher#launch(Object)}
|
||||
* @param input see {@link ActivityResultLauncher#launch(Object)}
|
||||
* @param tag Tag used for logging
|
||||
* @param context Context
|
||||
* @param <I> see {@link ActivityResultLauncher#launch(Object)}
|
||||
*/
|
||||
public static <I> void launchSafe(
|
||||
final ActivityResultLauncher<I> activityResultLauncher,
|
||||
final I input,
|
||||
final String tag,
|
||||
final Context context
|
||||
) {
|
||||
try {
|
||||
activityResultLauncher.launch(input);
|
||||
} catch (final ActivityNotFoundException aex) {
|
||||
Log.w(tag, "Unable to launch file/directory picker", aex);
|
||||
NoFileManagerSafeGuard.showActivityNotFoundAlert(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,10 +168,14 @@ public final class NavigationHelper {
|
|||
public static void playOnBackgroundPlayer(final Context context,
|
||||
final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
if (PlayerHolder.getInstance().getType() != MainPlayer.PlayerType.AUDIO) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
PlayerHolder.getInstance().getType() == PlayerType.AUDIO
|
||||
? R.string.background_player_already_playing_toast
|
||||
: R.string.background_player_playing_toast,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
|
||||
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal());
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler
|
|||
public final class TextLinkifier {
|
||||
public static final String TAG = TextLinkifier.class.getSimpleName();
|
||||
|
||||
private static final Pattern HASHTAGS_PATTERN = Pattern.compile("(#[A-Za-z0-9_]+)");
|
||||
// Looks for hashtags with characters from any language (\p{L}), numbers, or underscores
|
||||
private static final Pattern HASHTAGS_PATTERN =
|
||||
Pattern.compile("(#[\\p{L}0-9_]+)");
|
||||
|
||||
private TextLinkifier() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,10 +108,12 @@ public class FileStreamSAF extends SharpStream {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSetLength() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSeek() {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -131,10 +133,12 @@ public class FileStreamSAF extends SharpStream {
|
|||
out.write(buffer, offset, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLength(long length) throws IOException {
|
||||
channel.truncate(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long offset) throws IOException {
|
||||
channel.position(offset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ import com.google.android.material.snackbar.Snackbar;
|
|||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
|
@ -581,9 +581,9 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
|||
service = ErrorInfo.SERVICE_NONE;
|
||||
}
|
||||
|
||||
ErrorActivity.reportError(mContext,
|
||||
ErrorUtil.createNotification(mContext,
|
||||
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
|
||||
service, request.toString(), reason, null));
|
||||
service, request.toString(), reason));
|
||||
}
|
||||
|
||||
public void clearFinishedDownloads(boolean delete) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.nononsenseapps.filepicker.Utils;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ import us.shandian.giga.ui.adapter.MissionAdapter;
|
|||
|
||||
public class MissionsFragment extends Fragment {
|
||||
|
||||
private static final String TAG = "MissionsFragment";
|
||||
private static final int SPAN_SIZE = 2;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
|
|
@ -257,9 +259,13 @@ public class MissionsFragment extends Fragment {
|
|||
initialPath = Uri.parse(initialSavePath.getAbsolutePath());
|
||||
}
|
||||
|
||||
requestDownloadSaveAsLauncher.launch(
|
||||
NoFileManagerSafeGuard.launchSafe(
|
||||
requestDownloadSaveAsLauncher,
|
||||
StoredFileHelper.getNewPicker(mContext, mission.storage.getName(),
|
||||
mission.storage.getType(), initialPath));
|
||||
mission.storage.getType(), initialPath),
|
||||
TAG,
|
||||
mContext
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue