diff --git a/.editorconfig b/.editorconfig index e7c884c15..24476d4aa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,8 +12,6 @@ ktlint_function_naming_ignore_when_annotated_with = Composable ktlint_standard_argument-list-wrapping = disabled ktlint_standard_backing-property-naming = disabled -ktlint_standard_blank-line-before-declaration = disabled -ktlint_standard_blank-line-between-when-conditions = disabled ktlint_standard_chain-method-continuation = disabled ktlint_standard_class-signature = disabled ktlint_standard_function-expression-body = disabled diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 2bdea13c0..98cae7b30 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -193,18 +193,24 @@ class ErrorInfo private constructor( ErrorMessage(R.string.player_http_invalid_status, cause.responseCode.toString()) } } + cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessage(throwable, action, serviceId) + throwable.type == ExoPlaybackException.TYPE_SOURCE -> ErrorMessage(R.string.player_stream_failure) + throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> ErrorMessage(R.string.player_recoverable_failure) + else -> ErrorMessage(R.string.player_unrecoverable_failure) } } + throwable is FailedMediaSource.FailedMediaSourceException -> getMessage(throwable.cause, action, serviceId) + throwable is PlaybackResolver.ResolverException -> ErrorMessage(R.string.player_stream_failure) @@ -220,34 +226,46 @@ class ErrorInfo private constructor( ) } ?: ErrorMessage(R.string.account_terminated) + throwable is AgeRestrictedContentException -> ErrorMessage(R.string.restricted_video_no_stream) + throwable is GeographicRestrictionException -> ErrorMessage(R.string.georestricted_content) + throwable is PaidContentException -> ErrorMessage(R.string.paid_content) + throwable is PrivateContentException -> ErrorMessage(R.string.private_content) + throwable is SoundCloudGoPlusContentException -> ErrorMessage(R.string.soundcloud_go_plus_content) + throwable is UnsupportedContentInCountryException -> ErrorMessage(R.string.unsupported_content_in_country) + throwable is YoutubeMusicPremiumContentException -> ErrorMessage(R.string.youtube_music_premium_content) + throwable is SignInConfirmNotBotException -> ErrorMessage(R.string.sign_in_confirm_not_bot_error, getServiceName(serviceId)) + throwable is ContentNotAvailableException -> ErrorMessage(R.string.content_not_available) // other extractor exceptions throwable is ContentNotSupportedException -> ErrorMessage(R.string.content_not_supported) + // ReCaptchas will be handled in a special way anyway throwable is ReCaptchaException -> ErrorMessage(R.string.recaptcha_request_toast) + // test this at the end as many exceptions could be a subclass of IOException throwable != null && throwable.isNetworkRelated -> ErrorMessage(R.string.network_error) + // an extraction exception unrelated to the network // is likely an issue with parsing the website throwable is ExtractionException -> @@ -256,16 +274,22 @@ class ErrorInfo private constructor( // user actions (in case the exception is null or unrecognizable) action == UserAction.UI_ERROR -> ErrorMessage(R.string.app_ui_crash) + action == UserAction.REQUESTED_COMMENTS -> ErrorMessage(R.string.error_unable_to_load_comments) + action == UserAction.SUBSCRIPTION_CHANGE -> ErrorMessage(R.string.subscription_change_failed) + action == UserAction.SUBSCRIPTION_UPDATE -> ErrorMessage(R.string.subscription_update_failed) + action == UserAction.LOAD_IMAGE -> ErrorMessage(R.string.could_not_load_thumbnails) + action == UserAction.DOWNLOAD_OPEN_DIALOG -> ErrorMessage(R.string.could_not_setup_download_menu) + else -> ErrorMessage(R.string.error_snackbar_message) } @@ -276,15 +300,19 @@ class ErrorInfo private constructor( // we don't have an exception, so this is a manually built error, which likely // indicates that it's important and is thus reportable null -> true + // the service explicitly said that content is not available (e.g. age restrictions, // video deleted, etc.), there is no use in letting users report it is ContentNotAvailableException -> false + // we know the content is not supported, no need to let the user report it is ContentNotSupportedException -> false + // happens often when there is no internet connection; we don't use // `throwable.isNetworkRelated` since any `IOException` would make that function // return true, but not all `IOException`s are network related is UnknownHostException -> false + // by default, this is an unexpected exception, which the user could report else -> true } @@ -294,8 +322,10 @@ class ErrorInfo private constructor( return when (throwable) { // we know the content is not available, retrying won't help is ContentNotAvailableException -> false + // we know the content is not supported, retrying won't help is ContentNotSupportedException -> false + // by default (including if throwable is null), enable retrying (though the retry // button will be shown only if a way to perform the retry is implemented) else -> true diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index a39f31efd..e367961f1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -132,6 +132,7 @@ data class StreamItem( viewsAndDate.isEmpty() -> uploadDate!! else -> Localization.concatenateStrings(viewsAndDate, uploadDate) } + else -> viewsAndDate } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 1da3e277b..c60214564 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -85,9 +85,11 @@ class FeedLoadManager(private val context: Context) { FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions( outdatedThreshold ) + GROUP_NOTIFICATION_ENABLED -> feedDatabaseManager.outdatedSubscriptionsWithNotificationMode( outdatedThreshold, NotificationMode.ENABLED ) + else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) } @@ -305,6 +307,7 @@ class FeedLoadManager(private val context: Context) { feedDatabaseManager.markAsOutdated(info.uid) } } + notification.isOnError -> { val error = notification.error feedResultsHolder.addError(error!!) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index f913c845a..5a3753dd0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -276,10 +276,13 @@ class SubscriptionFragment : BaseStateFragment() { when (item) { is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupCardGridItem -> NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupAddNewItem -> FeedGroupDialog.newInstance().show(fm, null) + is FeedGroupAddNewGridItem -> FeedGroupDialog.newInstance().show(fm, null) } @@ -294,6 +297,7 @@ class SubscriptionFragment : BaseStateFragment() { when (item) { is FeedGroupCardItem -> FeedGroupDialog.newInstance(item.groupId).show(fm, null) + is FeedGroupCardGridItem -> FeedGroupDialog.newInstance(item.groupId).show(fm, null) } @@ -344,7 +348,9 @@ class SubscriptionFragment : BaseStateFragment() { 0 -> ShareUtils.shareText( requireContext(), selectedItem.name, selectedItem.url, selectedItem.thumbnails ) + 1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url) + 2 -> deleteChannel(selectedItem) } } @@ -404,6 +410,7 @@ class SubscriptionFragment : BaseStateFragment() { itemsListState = null } } + is SubscriptionState.ErrorState -> { result.error?.let { showError(ErrorInfo(result.error, UserAction.SOMETHING_ELSE, "Subscriptions")) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index c0783e812..57ff2a401 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -43,7 +43,9 @@ class SubscriptionManager(context: Context) { subscriptionTable.getSubscriptionsFiltered(filterQuery) } } + showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId) + else -> subscriptionTable.getAll() } } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 1cd297414..7cc9ba224 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -42,10 +42,12 @@ class MainPlayerGestureListener( v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) true } + MotionEvent.ACTION_UP -> { v.parent?.requestDisallowInterceptTouchEvent(false) false } + else -> true } } @@ -198,6 +200,7 @@ class MainPlayerGestureListener( when (PlayerHelper.getActionForRightGestureSide(player.context)) { player.context.getString(R.string.volume_control_key) -> onScrollVolume(distanceY) + player.context.getString(R.string.brightness_control_key) -> onScrollBrightness(distanceY) } @@ -205,6 +208,7 @@ class MainPlayerGestureListener( when (PlayerHelper.getActionForLeftGestureSide(player.context)) { player.context.getString(R.string.volume_control_key) -> onScrollVolume(distanceY) + player.context.getString(R.string.brightness_control_key) -> onScrollBrightness(distanceY) } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt index 0bbc7252d..c41f1dadd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt @@ -94,10 +94,13 @@ internal class PackageValidator(context: Context) { val isCallerKnown = when { // If it's our own app making the call, allow it. callingUid == Process.myUid() -> true + // If the system is making the call, allow it. callingUid == Process.SYSTEM_UID -> true + // If the app was signed by the same certificate as the platform itself, also allow it. callerSignature == platformSignature -> true + /* * [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and * while it isn't required to allow these apps to connect to a @@ -105,6 +108,7 @@ internal class PackageValidator(context: Context) { * such as Android TV and the Google Assistant. */ callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true + /* * If the calling app has a notification listener it is able to retrieve notifications * and can connect to an active [MediaSessionCompat]. diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 8154338ce..83cca2e0b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -122,10 +122,15 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { for ((key, value) in entries) { when (value) { is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is Set<*> -> { // There are currently only Sets with type String possible @Suppress("UNCHECKED_CAST") @@ -159,10 +164,15 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { for ((key, value) in jsonObject) { when (value) { is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is JsonArray -> { editor.putStringSet(key, value.mapNotNull { e -> e as? String }.toSet()) } diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt index 097200662..9dbfa826a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt @@ -88,6 +88,7 @@ class NotificationModeConfigFragment : Fragment() { toggleAll() true } + else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt index b5e17d63e..c7e94c7f8 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt +++ b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt @@ -92,6 +92,7 @@ object ImageStrategy { // the same number for those. val finalComparator = when (nonNoneQuality) { PreferredImageQuality.NONE -> initialComparator + PreferredImageQuality.LOW -> initialComparator.thenComparingDouble { image -> val pixelCount = estimatePixelCount(image, widthOverHeight) abs(pixelCount - BEST_LOW_H * BEST_LOW_H * widthOverHeight) diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt index 5383a613a..eb1153c7f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt @@ -37,7 +37,9 @@ object PoTokenProviderImpl : PoTokenProvider { webViewBadImpl = true return null } + null -> throw e + else -> throw cause // includes PoTokenException } } diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt index a76c5c31a..c99e2f639 100644 --- a/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt +++ b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt @@ -53,8 +53,10 @@ class TimestampLongPressClickableSpan( when (relatedInfoService) { ServiceList.YouTube -> return relatedStreamUrl + "&t=" + timestampMatchDTO.seconds() + ServiceList.SoundCloud, ServiceList.MediaCCC -> return relatedStreamUrl + "#t=" + timestampMatchDTO.seconds() + ServiceList.PeerTube -> return relatedStreamUrl + "?start=" + timestampMatchDTO.seconds() } diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt index 712e4ee0b..c145b8506 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt @@ -25,16 +25,19 @@ class MissionRecoveryInfo( isDesired2 = false kind = 'a' } + is VideoStream -> { desired = stream.getResolution() isDesired2 = stream.isVideoOnly() kind = 'v' } + is SubtitlesStream -> { desired = stream.languageTag isDesired2 = stream.isAutoGenerated kind = 's' } + else -> throw RuntimeException("Unknown stream kind") } } @@ -48,14 +51,17 @@ class MissionRecoveryInfo( str.append("audio") info = "bitrate=$desiredBitrate" } + 'v' -> { str.append("video") info = "quality=$desired videoOnly=$isDesired2" } + 's' -> { str.append("subtitles") info = "language=$desired autoGenerated=$isDesired2" } + else -> { info = "" str.append("other") diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt index a189640a6..628c41da4 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt @@ -93,6 +93,7 @@ class ImportAllCombinationsTest { Mockito.verify(editor, Mockito.atLeastOnce()) .putInt(Mockito.anyString(), Mockito.anyInt()) } + Ser.VULNERABLE -> runTest { Assert.assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) Assert.assertThrows(ClassNotFoundException::class.java) { @@ -102,6 +103,7 @@ class ImportAllCombinationsTest { Mockito.verify(editor, Mockito.never()).clear() Mockito.verify(editor, Mockito.never()).commit() } + Ser.NO -> runTest { Assert.assertFalse(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) Assert.assertThrows(IOException::class.java) {