Skip to content

Commit a369dee

Browse files
committed
Allow ErrorInfo messages with formatArgs
- ErrorInfo.getMessage() now returns an ErrorMessage instance that can be formatted into a string using a context (this allows the construction of an ErrorInfo to remain independent of a Context) - now the service ID is used in ErrorInfo.getMessage() to customize some messages based on the currently selected service - player HTTP invalid statuses are now included in the message - building a custom error message for AccountTerminatedException was moved from ErrorPanelHelper to ErrorInfo
1 parent 1bde2dc commit a369dee

File tree

72 files changed

+154
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+154
-146
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ dependencies {
214214
// the corresponding commit hash, since JitPack sometimes deletes artifacts.
215215
// If there’s already a git hash, just add more of it to the end (or remove a letter)
216216
// to cause jitpack to regenerate the artifact.
217-
implementation 'com.github.Stypox:NewPipeExtractor:b8bd4cda8cca00a14940933e3d3635d5aafec222'
217+
implementation 'com.github.TeamNewPipe:NewPipeExtractor:0023b22095a2d62a60cdfc87f4b5cd85c8b266c3'
218218
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
219219

220220
/** Checkstyle **/

app/src/main/java/org/schabi/newpipe/RouterActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ private static void handleError(final Context context, final ErrorInfo errorInfo
276276
|| errorInfo.getThrowable() instanceof ContentNotSupportedException) {
277277
// this exception does not usually indicate a problem that should be reported,
278278
// so just show a toast instead of the notification
279-
Toast.makeText(context, errorInfo.getMessageStringId(), Toast.LENGTH_LONG).show();
279+
Toast.makeText(context, errorInfo.getMessage(context), Toast.LENGTH_LONG).show();
280280
} else {
281281
ErrorUtil.createNotification(context, errorInfo);
282282
}

app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void send(@NonNull final Context context, @NonNull final CrashReportData
3636
ErrorUtil.openActivity(context, new ErrorInfo(
3737
new String[]{report.getString(ReportField.STACK_TRACE)},
3838
UserAction.UI_ERROR,
39-
ErrorInfo.SERVICE_NONE,
39+
null,
4040
"ACRA report",
4141
R.string.app_ui_crash));
4242
}

app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ protected void onCreate(final Bundle savedInstanceState) {
115115

116116
// normal bugreport
117117
buildInfo(errorInfo);
118-
activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId());
118+
activityErrorBinding.errorMessageView.setText(errorInfo.getMessage(this));
119119
activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces()));
120120

121121
// print stack trace once again for debugging:

app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt

Lines changed: 135 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package org.schabi.newpipe.error
22

3+
import android.content.Context
34
import android.os.Parcelable
45
import androidx.annotation.StringRes
6+
import androidx.core.content.ContextCompat
57
import com.google.android.exoplayer2.ExoPlaybackException
68
import com.google.android.exoplayer2.upstream.HttpDataSource
79
import com.google.android.exoplayer2.upstream.Loader
810
import kotlinx.parcelize.IgnoredOnParcel
911
import kotlinx.parcelize.Parcelize
1012
import org.schabi.newpipe.R
1113
import org.schabi.newpipe.extractor.Info
14+
import org.schabi.newpipe.extractor.ServiceList
15+
import org.schabi.newpipe.extractor.ServiceList.YouTube
1216
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
1317
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
1418
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
@@ -18,22 +22,21 @@ import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
1822
import org.schabi.newpipe.extractor.exceptions.PaidContentException
1923
import org.schabi.newpipe.extractor.exceptions.PrivateContentException
2024
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
25+
import org.schabi.newpipe.extractor.exceptions.SignInConfirmNotBotException
2126
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
2227
import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException
2328
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
24-
import org.schabi.newpipe.extractor.exceptions.YoutubeSignInConfirmNotBotException
2529
import org.schabi.newpipe.ktx.isNetworkRelated
2630
import org.schabi.newpipe.player.mediasource.FailedMediaSource
2731
import org.schabi.newpipe.player.resolver.PlaybackResolver
28-
import org.schabi.newpipe.util.ServiceHelper
2932

3033
@Parcelize
31-
class ErrorInfo(
34+
class ErrorInfo private constructor(
3235
val stackTraces: Array<String>,
3336
val userAction: UserAction,
34-
val serviceName: String,
37+
val serviceId: Int?,
3538
val request: String,
36-
val messageStringId: Int
39+
private val message: ErrorMessage,
3740
) : Parcelable {
3841

3942
// no need to store throwable, all data for report is in other variables
@@ -44,112 +47,191 @@ class ErrorInfo(
4447
private constructor(
4548
throwable: Throwable,
4649
userAction: UserAction,
47-
serviceName: String,
50+
serviceId: Int?,
4851
request: String
4952
) : this(
5053
throwableToStringList(throwable),
5154
userAction,
52-
serviceName,
55+
serviceId,
5356
request,
54-
getMessageStringId(throwable, userAction)
57+
getMessage(throwable, userAction, serviceId)
5558
) {
5659
this.throwable = throwable
5760
}
5861

5962
private constructor(
6063
throwable: List<Throwable>,
6164
userAction: UserAction,
62-
serviceName: String,
65+
serviceId: Int?,
6366
request: String
6467
) : this(
6568
throwableListToStringList(throwable),
6669
userAction,
67-
serviceName,
70+
serviceId,
6871
request,
69-
getMessageStringId(throwable.firstOrNull(), userAction)
72+
getMessage(throwable.firstOrNull(), userAction, serviceId)
7073
) {
7174
this.throwable = throwable.firstOrNull()
7275
}
7376

77+
// constructor to manually build ErrorInfo
78+
constructor(stackTraces: Array<String>, userAction: UserAction, serviceId: Int?, request: String, @StringRes message: Int) :
79+
this(stackTraces, userAction, serviceId, request, ErrorMessage(message))
80+
7481
// constructors with single throwable
7582
constructor(throwable: Throwable, userAction: UserAction, request: String) :
76-
this(throwable, userAction, SERVICE_NONE, request)
83+
this(throwable, userAction, null, request)
7784
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) :
78-
this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
85+
this(throwable, userAction, serviceId, request)
7986
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) :
80-
this(throwable, userAction, getInfoServiceName(info), request)
87+
this(throwable, userAction, info?.serviceId, request)
8188

8289
// constructors with list of throwables
8390
constructor(throwable: List<Throwable>, userAction: UserAction, request: String) :
84-
this(throwable, userAction, SERVICE_NONE, request)
91+
this(throwable, userAction, null, request)
8592
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) :
86-
this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
93+
this(throwable, userAction, serviceId, request)
8794
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) :
88-
this(throwable, userAction, getInfoServiceName(info), request)
95+
this(throwable, userAction, info?.serviceId, request)
96+
97+
fun getServiceName(): String {
98+
return getServiceName(serviceId)
99+
}
100+
101+
fun getMessage(context: Context): String {
102+
return message.getString(context)
103+
}
89104

90105
companion object {
91-
const val SERVICE_NONE = "none"
106+
@Parcelize
107+
class ErrorMessage(
108+
@StringRes
109+
private val stringRes: Int,
110+
private vararg val formatArgs: String,
111+
) : Parcelable {
112+
fun getString(context: Context): String {
113+
return if (formatArgs.isEmpty()) {
114+
// use ContextCompat.getString() just in case context is not AppCompatActivity
115+
ContextCompat.getString(context, stringRes)
116+
} else {
117+
// ContextCompat.getString() with formatArgs does not exist, so we just
118+
// replicate its source code but with formatArgs
119+
ContextCompat.getContextForLanguage(context).getString(stringRes, *formatArgs)
120+
}
121+
}
122+
}
123+
124+
const val SERVICE_NONE = "<unknown_service>"
125+
126+
private fun getServiceName(serviceId: Int?) =
127+
// not using getNameOfServiceById since we want to accept a nullable serviceId and we
128+
// want to default to SERVICE_NONE
129+
ServiceList.all()?.firstOrNull { it.serviceId == serviceId }?.serviceInfo?.name
130+
?: SERVICE_NONE
92131

93132
fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString())
94133

95134
fun throwableListToStringList(throwableList: List<Throwable>) =
96135
throwableList.map { it.stackTraceToString() }.toTypedArray()
97136

98-
private fun getInfoServiceName(info: Info?) =
99-
if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId)
100-
101-
@StringRes
102-
fun getMessageStringId(
137+
fun getMessage(
103138
throwable: Throwable?,
104-
action: UserAction?
105-
): Int {
139+
action: UserAction?,
140+
serviceId: Int?,
141+
): ErrorMessage {
106142
return when {
107143
// player exceptions
108144
// some may be IOException, so do these checks before isNetworkRelated!
109145
throwable is ExoPlaybackException -> {
110146
val cause = throwable.cause
111147
when {
112-
cause is HttpDataSource.InvalidResponseCodeException && cause.responseCode == 403 -> R.string.player_error_403
113-
cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessageStringId(throwable, action)
114-
throwable.type == ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure
115-
throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure
116-
else -> R.string.player_unrecoverable_failure
148+
cause is HttpDataSource.InvalidResponseCodeException -> {
149+
if (cause.responseCode == 403) {
150+
if (serviceId == YouTube.serviceId) {
151+
ErrorMessage(R.string.youtube_player_http_403)
152+
} else {
153+
ErrorMessage(R.string.player_http_403)
154+
}
155+
} else {
156+
ErrorMessage(R.string.player_http_invalid_status, cause.responseCode.toString())
157+
}
158+
}
159+
cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException ->
160+
getMessage(throwable, action, serviceId)
161+
throwable.type == ExoPlaybackException.TYPE_SOURCE ->
162+
ErrorMessage(R.string.player_stream_failure)
163+
throwable.type == ExoPlaybackException.TYPE_UNEXPECTED ->
164+
ErrorMessage(R.string.player_recoverable_failure)
165+
else ->
166+
ErrorMessage(R.string.player_unrecoverable_failure)
117167
}
118168
}
119-
throwable is FailedMediaSource.FailedMediaSourceException -> getMessageStringId(throwable.cause, action)
120-
throwable is PlaybackResolver.ResolverException -> R.string.player_stream_failure
169+
throwable is FailedMediaSource.FailedMediaSourceException ->
170+
getMessage(throwable.cause, action, serviceId)
171+
throwable is PlaybackResolver.ResolverException ->
172+
ErrorMessage(R.string.player_stream_failure)
121173

122174
// content not available exceptions
123-
throwable is AccountTerminatedException -> R.string.account_terminated
124-
throwable is AgeRestrictedContentException -> R.string.restricted_video_no_stream
125-
throwable is GeographicRestrictionException -> R.string.georestricted_content
126-
throwable is PaidContentException -> R.string.paid_content
127-
throwable is PrivateContentException -> R.string.private_content
128-
throwable is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
129-
throwable is UnsupportedContentInCountryException -> R.string.unsupported_content_in_country
130-
throwable is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
131-
throwable is YoutubeSignInConfirmNotBotException -> R.string.youtube_sign_in_confirm_not_bot_error
132-
throwable is ContentNotAvailableException -> R.string.content_not_available
175+
throwable is AccountTerminatedException ->
176+
throwable.message
177+
?.takeIf { reason -> !reason.isEmpty() }
178+
?.let { reason ->
179+
ErrorMessage(
180+
R.string.account_terminated_service_provides_reason,
181+
getServiceName(serviceId),
182+
reason
183+
)
184+
}
185+
?: ErrorMessage(R.string.account_terminated)
186+
throwable is AgeRestrictedContentException ->
187+
ErrorMessage(R.string.restricted_video_no_stream)
188+
throwable is GeographicRestrictionException ->
189+
ErrorMessage(R.string.georestricted_content)
190+
throwable is PaidContentException ->
191+
ErrorMessage(R.string.paid_content)
192+
throwable is PrivateContentException ->
193+
ErrorMessage(R.string.private_content)
194+
throwable is SoundCloudGoPlusContentException ->
195+
ErrorMessage(R.string.soundcloud_go_plus_content)
196+
throwable is UnsupportedContentInCountryException ->
197+
ErrorMessage(R.string.unsupported_content_in_country)
198+
throwable is YoutubeMusicPremiumContentException ->
199+
ErrorMessage(R.string.youtube_music_premium_content)
200+
throwable is SignInConfirmNotBotException ->
201+
ErrorMessage(R.string.sign_in_confirm_not_bot_error, getServiceName(serviceId))
202+
throwable is ContentNotAvailableException ->
203+
ErrorMessage(R.string.content_not_available)
133204

134205
// other extractor exceptions
135-
throwable is ContentNotSupportedException -> R.string.content_not_supported
206+
throwable is ContentNotSupportedException ->
207+
ErrorMessage(R.string.content_not_supported)
136208
// ReCaptchas should have already been handled elsewhere,
137209
// but return an error message here just in case
138-
throwable is ReCaptchaException -> R.string.recaptcha_request_toast
210+
throwable is ReCaptchaException ->
211+
ErrorMessage(R.string.recaptcha_request_toast)
139212
// test this at the end as many exceptions could be a subclass of IOException
140-
throwable != null && throwable.isNetworkRelated -> R.string.network_error
213+
throwable != null && throwable.isNetworkRelated ->
214+
ErrorMessage(R.string.network_error)
141215
// an extraction exception unrelated to the network
142216
// is likely an issue with parsing the website
143-
throwable is ExtractionException -> R.string.parsing_error
217+
throwable is ExtractionException ->
218+
ErrorMessage(R.string.parsing_error)
144219

145220
// user actions (in case the exception is null or unrecognizable)
146-
action == UserAction.UI_ERROR -> R.string.app_ui_crash
147-
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
148-
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
149-
action == UserAction.SUBSCRIPTION_UPDATE -> R.string.subscription_update_failed
150-
action == UserAction.LOAD_IMAGE -> R.string.could_not_load_thumbnails
151-
action == UserAction.DOWNLOAD_OPEN_DIALOG -> R.string.could_not_setup_download_menu
152-
else -> R.string.error_snackbar_message
221+
action == UserAction.UI_ERROR ->
222+
ErrorMessage(R.string.app_ui_crash)
223+
action == UserAction.REQUESTED_COMMENTS ->
224+
ErrorMessage(R.string.error_unable_to_load_comments)
225+
action == UserAction.SUBSCRIPTION_CHANGE ->
226+
ErrorMessage(R.string.subscription_change_failed)
227+
action == UserAction.SUBSCRIPTION_UPDATE ->
228+
ErrorMessage(R.string.subscription_update_failed)
229+
action == UserAction.LOAD_IMAGE ->
230+
ErrorMessage(R.string.could_not_load_thumbnails)
231+
action == UserAction.DOWNLOAD_OPEN_DIALOG ->
232+
ErrorMessage(R.string.could_not_setup_download_menu)
233+
else ->
234+
ErrorMessage(R.string.error_snackbar_message)
153235
}
154236
}
155237
}

app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
1414
import io.reactivex.rxjava3.disposables.Disposable
1515
import org.schabi.newpipe.MainActivity
1616
import org.schabi.newpipe.R
17-
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
1817
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
1918
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
2019
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
21-
import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
2220
import org.schabi.newpipe.ktx.animate
2321
import org.schabi.newpipe.ktx.isInterruptedCaused
24-
import org.schabi.newpipe.util.ServiceHelper
2522
import org.schabi.newpipe.util.external_communication.ShareUtils
2623
import java.util.concurrent.TimeUnit
2724

@@ -99,28 +96,14 @@ class ErrorPanelHelper(
9996

10097
errorRetryButton.isVisible = retryShouldBeShown
10198
showAndSetOpenInBrowserButtonAction(errorInfo)
102-
} else if (errorInfo.throwable is AccountTerminatedException) {
103-
errorTextView.setText(R.string.account_terminated)
104-
105-
if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) {
106-
errorServiceInfoTextView.text = context.resources.getString(
107-
R.string.service_provides_reason,
108-
ServiceHelper.getSelectedService(context)?.serviceInfo?.name ?: "<unknown>"
109-
)
110-
errorServiceInfoTextView.isVisible = true
111-
112-
errorServiceExplanationTextView.text =
113-
(errorInfo.throwable as AccountTerminatedException).message
114-
errorServiceExplanationTextView.isVisible = true
115-
}
11699
} else {
117100
showAndSetErrorButtonAction(
118101
R.string.error_snackbar_action
119102
) {
120103
ErrorUtil.openActivity(context, errorInfo)
121104
}
122105

123-
errorTextView.setText(errorInfo.messageStringId)
106+
errorTextView.text = errorInfo.getMessage(context)
124107

125108
if (errorInfo.throwable !is ContentNotAvailableException &&
126109
errorInfo.throwable !is ContentNotSupportedException

0 commit comments

Comments
 (0)