From a57fd69fb4787d08982c6cfb0d867f365588d6dc Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Tue, 15 Dec 2020 20:11:55 +0100 Subject: [PATCH 1/3] External sharing improvements Improve NewPipe's share on some devices + fix crash when no browser is set on some devices Catching ActivityNotFoundException when trying to open the default browser Use an ACTION_CHOOSER intent and put as an extra intent the intent to open an URI / share an URI when no default app is set. Add a LinkHelper class which set a custom action when clicking web links in the description of a content. This class also helps to implement a confirmation dialog when trying to open web links in an external app. --- .../schabi/newpipe/about/AboutActivity.java | 12 +- .../fragments/detail/VideoDetailFragment.java | 28 ++-- .../list/channel/ChannelFragment.java | 5 +- .../schabi/newpipe/report/ErrorActivity.java | 14 +- .../util/CommentTextOnTouchListener.java | 3 +- .../schabi/newpipe/util/ExtractorHelper.java | 4 +- .../org/schabi/newpipe/util/LinkHelper.java | 116 +++++++++++++++ .../schabi/newpipe/util/NavigationHelper.java | 27 +--- .../org/schabi/newpipe/util/ShareUtils.java | 136 ++++++++++++++---- .../giga/ui/adapter/MissionAdapter.java | 30 ++-- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + checkstyle-suppressions.xml | 17 ++- 13 files changed, 284 insertions(+), 110 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/LinkHelper.java diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java index 2569f4a9404..d11978c6b74 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java @@ -146,16 +146,20 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME); aboutBinding.githubLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.github_url))); + openUrlInBrowser(context, context.getString(R.string.github_url), + false)); aboutBinding.donationLink.setOnClickListener(v -> - openUrlInBrowser(context, context.getString(R.string.donation_url))); + openUrlInBrowser(context, context.getString(R.string.donation_url), + false)); aboutBinding.websiteLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.website_url))); + openUrlInBrowser(context, context.getString(R.string.website_url), + false)); aboutBinding.privacyPolicyLink.setOnClickListener(v -> - openUrlInBrowser(context, context.getString(R.string.privacy_policy_url))); + openUrlInBrowser(context, context.getString(R.string.privacy_policy_url), + false)); return aboutBinding.getRoot(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 8c9ed2bef5c..c9913a80853 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -16,7 +16,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; -import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; @@ -96,6 +95,7 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.LinkHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -112,10 +112,7 @@ import java.util.concurrent.TimeUnit; import icepick.State; -import io.noties.markwon.Markwon; -import io.noties.markwon.linkify.LinkifyPlugin; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -1233,26 +1230,17 @@ private void prepareDescription(final Description description) { } if (description.getType() == Description.HTML) { - disposables.add(Single.just(description.getContent()) - .map(descriptionText -> - HtmlCompat.fromHtml(descriptionText, - HtmlCompat.FROM_HTML_MODE_LEGACY)) - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(spanned -> { - videoDescriptionView.setText(spanned); - videoDescriptionView.setVisibility(View.VISIBLE); - })); + LinkHelper.createLinksFromHtmlBlock(requireContext(), description.getContent(), + videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY); + videoDescriptionView.setVisibility(View.VISIBLE); } else if (description.getType() == Description.MARKDOWN) { - final Markwon markwon = Markwon.builder(requireContext()) - .usePlugin(LinkifyPlugin.create()) - .build(); - markwon.setMarkdown(videoDescriptionView, description.getContent()); + LinkHelper.createLinksFromMarkdownText(requireContext(), description.getContent(), + videoDescriptionView); videoDescriptionView.setVisibility(View.VISIBLE); } else { //== Description.PLAIN_TEXT - videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS); - videoDescriptionView.setText(description.getContent(), TextView.BufferType.SPANNABLE); + LinkHelper.createLinksFromPlainText(requireContext(), description.getContent(), + videoDescriptionView); videoDescriptionView.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 834d8052fd6..6bcaac63036 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -1,8 +1,6 @@ package org.schabi.newpipe.fragments.list.channel; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -188,8 +186,7 @@ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { private void openRssFeed() { final ChannelInfo info = currentInfo; if (info != null) { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl())); - startActivity(intent); + ShareUtils.openUrlInBrowser(requireContext(), info.getFeedUrl(), false); } } diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index a4b6af2ab01..0321d197a72 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -14,7 +14,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -207,8 +206,7 @@ protected void onCreate(final Bundle savedInstanceState) { openPrivacyPolicyDialog(this, "EMAIL")); activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> { - ShareUtils.copyToClipboard(this, buildMarkdown()); - Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show(); + ShareUtils.copyToClipboard(this, buildMarkdown()); }); activityErrorBinding.errorReportGitHubButton.setOnClickListener(v -> @@ -246,11 +244,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { goToReturnActivity(); break; case R.id.menu_item_share_error: - final Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, buildJson()); - intent.setType("text/plain"); - startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); + ShareUtils.shareUrl(this, getString(R.string.error_report_title), buildJson()); break; } return false; @@ -273,10 +267,10 @@ private void openPrivacyPolicyDialog(final Context context, final String action) .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) .putExtra(Intent.EXTRA_TEXT, buildJson()); if (i.resolveActivity(getPackageManager()) != null) { - startActivity(i); + ShareUtils.openContentInApp(context, i); } } else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub - ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL); + ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false); } }) diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index 36f39a6e554..5c7eb4c3f92 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -69,7 +69,8 @@ public boolean onTouch(final View v, final MotionEvent event) { handled = handleUrl(v.getContext(), (URLSpan) link[0]); } if (!handled) { - link[0].onClick(widget); + ShareUtils.openUrlInBrowser(v.getContext(), + ((URLSpan) link[0]).getURL(), false); } } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 103d9a72b95..7046b8e49a9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -365,8 +365,8 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf } } - metaInfoTextView.setText(HtmlCompat.fromHtml(stringBuilder.toString(), - HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING)); + LinkHelper.createLinksFromHtmlBlock(context, stringBuilder.toString(), + metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance()); metaInfoTextView.setVisibility(View.VISIBLE); metaInfoSeparator.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java b/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java new file mode 100644 index 00000000000..fba30758fc7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java @@ -0,0 +1,116 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.text.util.Linkify; +import android.view.View; +import android.widget.TextView; + +import androidx.core.text.HtmlCompat; + +import io.noties.markwon.Markwon; +import io.noties.markwon.linkify.LinkifyPlugin; + +public final class LinkHelper { + private LinkHelper() { + } + + /** + * Create web links for contents with an HTML description. + *

+ * This will call + * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. + * + * @param context the context to use + * @param htmlBlock the htmlBlock to be linked + * @param textView the TextView to set the htmlBlock linked + * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)} + * will be called + */ + public static void createLinksFromHtmlBlock(final Context context, + final String htmlBlock, + final TextView textView, + final int htmlCompatFlag) { + changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), + textView); + } + + /** + * Create web links for contents with a plain text description. + *

+ * This will call + * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and + * {@link TextView#setText(CharSequence, TextView.BufferType)}. + * + * @param context the context to use + * @param plainTextBlock the block of plain text to be linked + * @param textView the TextView to set the plain text block linked + */ + public static void createLinksFromPlainText(final Context context, + final String plainTextBlock, + final TextView textView) { + textView.setAutoLinkMask(Linkify.WEB_URLS); + textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); + changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + } + + /** + * Create web links for contents with a markdown description. + *

+ * This will call + * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after creating an {@link Markwon} object and using + * {@link Markwon#setMarkdown(TextView, String)}. + * + * @param context the context to use + * @param markdownBlock the block of markdown text to be linked + * @param textView the TextView to set the plain text block linked + */ + public static void createLinksFromMarkdownText(final Context context, + final String markdownBlock, + final TextView textView) { + final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build(); + markwon.setMarkdown(textView, markdownBlock); + changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + } + + /** + * Change links generated by libraries in the description of a content to a custom link action. + *

+ * Instead of using an ACTION_VIEW intent in the description of a content, this method will + * parse the CharSequence and replace all current web links with + * {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)}. + *

+ * This method is required in order to intercept links and maybe, show a confirmation dialog + * before opening a web link. + * + * @param context the context to use + * @param chars the CharSequence to be parsed + * @param textView the TextView in which the converted CharSequence will be applied + */ + private static void changeIntentsOfDescriptionLinks(final Context context, + final CharSequence chars, + final TextView textView) { + final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); + final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); + + for (final URLSpan span : urls) { + final ClickableSpan clickableSpan = new ClickableSpan() { + public void onClick(final View view) { + ShareUtils.openUrlInBrowser(context, span.getURL(), false); + } + }; + textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), + textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); + textBlockLinked.removeSpan(span); + } + + textView.setText(textBlockLinked); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index c90bb302577..936b4595cb4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -246,16 +246,14 @@ public static void playOnExternalPlayer(final Context context, final String name public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) { if (intent.resolveActivity(context.getPackageManager()) != null) { - context.startActivity(intent); + ShareUtils.openContentInApp(context, intent); } else { if (context instanceof Activity) { new AlertDialog.Builder(context) .setMessage(R.string.no_player_found) .setPositiveButton(R.string.install, (dialog, which) -> { - final Intent i = new Intent(); - i.setAction(Intent.ACTION_VIEW); - i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url))); - context.startActivity(i); + ShareUtils.openUrlInBrowser(context, + context.getString(R.string.fdroid_vlc_url), false); }) .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) @@ -568,27 +566,14 @@ public static Intent getIntentByLink(final Context context, return getOpenIntent(context, url, service.getServiceId(), linkType); } - private static Uri openMarketUrl(final String packageName) { - return Uri.parse("market://details") - .buildUpon() - .appendQueryParameter("id", packageName) - .build(); - } - - private static Uri getGooglePlayUrl(final String packageName) { - return Uri.parse("https://play.google.com/store/apps/details") - .buildUpon() - .appendQueryParameter("id", packageName) - .build(); - } - private static void installApp(final Context context, final String packageName) { try { // Try market:// scheme - context.startActivity(new Intent(Intent.ACTION_VIEW, openMarketUrl(packageName))); + ShareUtils.openUrlInBrowser(context, "market://details?id=" + packageName, false); } catch (final ActivityNotFoundException e) { // Fall back to google play URL (don't worry F-Droid can handle it :) - context.startActivity(new Intent(Intent.ACTION_VIEW, getGooglePlayUrl(packageName))); + ShareUtils.openUrlInBrowser(context, + "https://play.google.com/store/apps/details?id=" + packageName, false); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java index b631f19da53..142b2b20cc5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.util; +import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -21,22 +22,80 @@ private ShareUtils() { * Open the url with the system default browser. *

* If no browser is set as default, fallbacks to - * {@link ShareUtils#openInDefaultApp(Context, String)} + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + * + * @param context the context to use + * @param url the url to browse + * @param httpDefaultBrowserTest the boolean to set if the + * test for the default browser will be for HTTP protocol + * or for the created intent + */ + public static void openUrlInBrowser(final Context context, final String url, + final Boolean httpDefaultBrowserTest) { + final String defaultPackageName; + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (httpDefaultBrowserTest) { + defaultPackageName = getDefaultBrowserPackageName(context); + } else { + defaultPackageName = getDefaultAppPackageName(context, intent); + } + + if (defaultPackageName.equals("android")) { + // no browser set as default (doesn't work on some devices) + openInDefaultApp(context, intent); + } else { + try { + intent.setPackage(defaultPackageName); + context.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // not a browser but an app chooser because of OEMs changes + intent.setPackage(null); + openInDefaultApp(context, intent); + } + } + } + + /** + * Open the url with the system default browser. + *

+ * If no browser is set as default, fallbacks to + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + *

+ * This call {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)} with true + * for the boolean parameter * * @param context the context to use * @param url the url to browse - */ + **/ public static void openUrlInBrowser(final Context context, final String url) { - final String defaultBrowserPackageName = getDefaultBrowserPackageName(context); + openUrlInBrowser(context, url, true); + } + + /** + * Open a content with the system default browser. + *

+ * If no app is set as default, fallbacks to + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + * + * @param context the context to use + * @param intent the intent of the file to open + */ + public static void openContentInApp(final Context context, final Intent intent) { + final String defaultAppPackageName = getDefaultAppPackageName(context, intent); - if (defaultBrowserPackageName.equals("android")) { - // no browser set as default - openInDefaultApp(context, url); + if (defaultAppPackageName.equals("android")) { + // no app set as default (doesn't work on some devices) + openInDefaultApp(context, intent); } else { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) - .setPackage(defaultBrowserPackageName) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + try { + intent.setPackage(defaultAppPackageName); + context.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // not an app to open a file but an app chooser because of OEMs changes + intent.setPackage(null); + openInDefaultApp(context, intent); + } } } @@ -45,20 +104,38 @@ public static void openUrlInBrowser(final Context context, final String url) { *

* If no app is set as default, it will open a chooser * + * @param context the context to use + * @param viewIntent the intent to open + */ + private static void openInDefaultApp(final Context context, final Intent viewIntent) { + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, viewIntent); + intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * Get the default app package name. + *

+ * If no app is set as default, it will return "android". + *

+ * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser. + * * @param context the context to use - * @param url the url to browse + * @param intent the intent to get default app + * @return the package name of the default app, or the app chooser if there's no default */ - private static void openInDefaultApp(final Context context, final String url) { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(Intent.createChooser( - intent, context.getString(R.string.share_dialog_title)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + private static String getDefaultAppPackageName(final Context context, final Intent intent) { + return context.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName; } /** * Get the default browser package name. *

* If no browser is set as default, it will return "android" + * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser. * * @param context the context to use * @return the package name of the default browser, or "android" if there's no default @@ -66,8 +143,8 @@ private static void openInDefaultApp(final Context context, final String url) { private static String getDefaultBrowserPackageName(final Context context) { final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity( - intent, PackageManager.MATCH_DEFAULT_ONLY); + final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY); return resolveInfo.activityInfo.packageName; } @@ -79,13 +156,15 @@ private static String getDefaultBrowserPackageName(final Context context) { * @param url the url to share */ public static void shareUrl(final Context context, final String subject, final String url) { - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, url); - context.startActivity(Intent.createChooser( - intent, context.getString(R.string.share_dialog_title)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + final Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + shareIntent.putExtra(Intent.EXTRA_TEXT, url); + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, shareIntent); + intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } /** @@ -100,14 +179,11 @@ public static void copyToClipboard(final Context context, final String text) { ContextCompat.getSystemService(context, ClipboardManager.class); if (clipboardManager == null) { - Toast.makeText(context, - R.string.permission_denied, - Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show(); return; } clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text)); - Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT) - .show(); + Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index f102206c132..79b91cb5782 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.NotificationManager; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -44,6 +45,7 @@ import org.schabi.newpipe.report.ErrorInfo; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ShareUtils; import java.io.File; import java.net.URI; @@ -346,10 +348,9 @@ private void viewWithFileProvider(Mission mission) { if (BuildConfig.DEBUG) Log.v(TAG, "Mime: " + mimeType + " package: " + BuildConfig.APPLICATION_ID + ".provider"); - Uri uri = resolveShareableUri(mission); + final Uri uri = resolveShareableUri(mission); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); + Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, mimeType); intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); @@ -363,7 +364,7 @@ private void viewWithFileProvider(Mission mission) { //mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); if (intent.resolveActivity(mContext.getPackageManager()) != null) { - mContext.startActivity(intent); + ShareUtils.openContentInApp(mContext, intent); } else { Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show(); } @@ -372,12 +373,23 @@ private void viewWithFileProvider(Mission mission) { private void shareFile(Mission mission) { if (checkInvalidFile(mission)) return; - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType(resolveMimeType(mission)); - intent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission)); - intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + final Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType(resolveMimeType(mission)); + shareIntent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission)); + shareIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, shareIntent); + intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(Intent.createChooser(intent, null)); + try { + intent.setPackage("android"); + mContext.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // falling back to OEM chooser if Android's system chooser was removed by the OEM + intent.setPackage(null); + mContext.startActivity(intent); + } } /** diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c128c42149e..a6b64da7120 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -651,4 +651,5 @@ Demander à Android de personnaliser la couleur de la notification en fonction de la couleur principale de la miniature (noter que cela n’est pas disponible sur tous les appareils) Afficher la miniature Utiliser la miniature pour l\'arrière-plan de l’écran de verrouillage et les notifications + Ouvrir avec \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ac951b695e..82a5bda6c62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc Open in browser Open in popup mode + Open with Share Download Download stream file diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 400a91a2918..0aca3c4ab13 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -12,21 +12,20 @@ lines="253,325"/> + files="ListHelper.java" + lines="281,313"/> + files="ContentSettingsFragment.java" + lines="227,245"/> - + files="WebMWriter.java" + lines="156,158"/> + files="Player.java"/> + files="VideoDetailFragment.java"/> From 79e98db3bd58adfe2bc298ce672e963587facf33 Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Fri, 15 Jan 2021 17:11:04 +0100 Subject: [PATCH 2/3] Apply the requested changes and little improvements Apply the requested changes, use ShareUtils.shareText to share an stream in the play queue and optimize imports for Java files, using Android Studio functionality. Apply the requested changes and do little improvements Apply the requested changes, use ShareUtils.shareText to share an stream in the play queue and optimize imports for Java files, using Android Studio functionality. --- .../material/appbar/FlingBehavior.java | 1 + app/src/main/java/org/schabi/newpipe/App.java | 31 +++++----- .../org/schabi/newpipe/DownloaderImpl.java | 2 +- .../org/schabi/newpipe/ImageDownloader.java | 1 + .../java/org/schabi/newpipe/MainActivity.java | 14 +++-- .../org/schabi/newpipe/RouterActivity.java | 2 +- .../schabi/newpipe/about/AboutActivity.java | 9 +-- .../fragments/detail/VideoDetailFragment.java | 8 +-- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../holder/CommentsMiniInfoItemHolder.java | 3 +- .../holder/StreamInfoItemHolder.java | 3 +- .../newpipe/local/BaseLocalListFragment.java | 5 +- .../local/bookmark/BookmarkFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 2 +- .../org/schabi/newpipe/player/MainPlayer.java | 2 +- .../newpipe/player/PlayQueueActivity.java | 15 +---- .../org/schabi/newpipe/player/Player.java | 2 +- .../event/CustomBottomSheetBehavior.java | 3 + .../helper/PlaybackParameterDialog.java | 2 +- .../newpipe/player/playqueue/PlayQueue.java | 2 +- .../schabi/newpipe/report/ErrorActivity.java | 4 +- .../settings/BasePreferenceFragment.java | 2 +- .../settings/SelectChannelFragment.java | 2 +- .../custom/NotificationActionsPreference.java | 5 +- .../newpipe/settings/tabs/TabsManager.java | 3 +- .../util/CommentTextOnTouchListener.java | 2 +- .../schabi/newpipe/util/ExtractorHelper.java | 2 +- .../schabi/newpipe/util/FilenameUtils.java | 1 + .../org/schabi/newpipe/util/ListHelper.java | 5 +- .../org/schabi/newpipe/util/Localization.java | 15 +++-- .../schabi/newpipe/util/NavigationHelper.java | 18 ++---- .../schabi/newpipe/util/PeertubeHelper.java | 1 + .../schabi/newpipe/util/ServiceHelper.java | 2 +- .../org/schabi/newpipe/util/ShareUtils.java | 56 +++++++++++++------ .../newpipe/util/StreamDialogEntry.java | 2 +- .../{LinkHelper.java => TextLinkifier.java} | 13 +++-- .../org/schabi/newpipe/util/ThemeHelper.java | 2 +- .../views/CustomCollapsingToolbarLayout.java | 2 + .../newpipe/views/ExpandableSurfaceView.java | 1 + .../newpipe/views/FocusAwareCoordinator.java | 3 +- .../giga/service/DownloadManagerService.java | 7 +-- .../giga/ui/adapter/MissionAdapter.java | 2 +- .../giga/ui/fragment/MissionsFragment.java | 2 +- checkstyle-suppressions.xml | 2 +- 45 files changed, 145 insertions(+), 122 deletions(-) rename app/src/main/java/org/schabi/newpipe/util/{LinkHelper.java => TextLinkifier.java} (92%) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 6106d04370b..743ff1ff20e 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import org.schabi.newpipe.R; import java.lang.reflect.Field; diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 342ce3498a9..cd4a04d9abe 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -6,26 +6,16 @@ import android.content.SharedPreferences; import android.os.Build; import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; + import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.exceptions.CompositeException; -import io.reactivex.rxjava3.exceptions.MissingBackpressureException; -import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; -import io.reactivex.rxjava3.exceptions.UndeliverableException; -import io.reactivex.rxjava3.functions.Consumer; -import io.reactivex.rxjava3.plugins.RxJavaPlugins; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.SocketException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; + import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; @@ -41,6 +31,21 @@ import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.CompositeException; +import io.reactivex.rxjava3.exceptions.MissingBackpressureException; +import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; +import io.reactivex.rxjava3.exceptions.UndeliverableException; +import io.reactivex.rxjava3.functions.Consumer; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + /* * Copyright (C) Hans-Christoph Steiner 2016 * App.java is part of NewPipe. diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 431b8034acf..50972fb2f37 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -2,10 +2,10 @@ import android.content.Context; import android.os.Build; -import androidx.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Request; diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java index c2897cff19d..ceae117778d 100644 --- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java +++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; + import androidx.preference.PreferenceManager; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 5cecbc6d239..27720921145 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -20,8 +20,6 @@ package org.schabi.newpipe; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +41,7 @@ import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.Spinner; + import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -53,10 +52,9 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; + import com.google.android.material.bottomsheet.BottomSheetBehavior; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; + import org.schabi.newpipe.databinding.ActivityMainBinding; import org.schabi.newpipe.databinding.DrawerHeaderBinding; import org.schabi.newpipe.databinding.DrawerLayoutBinding; @@ -89,6 +87,12 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 98a0921e4ea..7f935d0070e 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -188,7 +188,7 @@ private void showUnsupportedUrlDialog(final String url) { .setPositiveButton(R.string.open_in_browser, (dialog, which) -> ShareUtils.openUrlInBrowser(this, url)) .setNegativeButton(R.string.share, - (dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject + (dialog, which) -> ShareUtils.shareText(this, "", url)) // no subject .setNeutralButton(R.string.cancel, null) .setOnDismissListener(dialog -> finish()) .show(); diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java index d11978c6b74..ec28be237ef 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java @@ -146,16 +146,13 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME); aboutBinding.githubLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.github_url), - false)); + openUrlInBrowser(context, context.getString(R.string.github_url), false)); aboutBinding.donationLink.setOnClickListener(v -> - openUrlInBrowser(context, context.getString(R.string.donation_url), - false)); + openUrlInBrowser(context, context.getString(R.string.donation_url), false)); aboutBinding.websiteLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.website_url), - false)); + openUrlInBrowser(context, context.getString(R.string.website_url), false)); aboutBinding.privacyPolicyLink.setOnClickListener(v -> openUrlInBrowser(context, context.getString(R.string.privacy_policy_url), diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index c9913a80853..7ebf6639079 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -95,12 +95,12 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; -import org.schabi.newpipe.util.LinkHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ShareUtils; +import org.schabi.newpipe.util.TextLinkifier; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.LargeTextMovementMethod; @@ -1230,16 +1230,16 @@ private void prepareDescription(final Description description) { } if (description.getType() == Description.HTML) { - LinkHelper.createLinksFromHtmlBlock(requireContext(), description.getContent(), + TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(), videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY); videoDescriptionView.setVisibility(View.VISIBLE); } else if (description.getType() == Description.MARKDOWN) { - LinkHelper.createLinksFromMarkdownText(requireContext(), description.getContent(), + TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(), videoDescriptionView); videoDescriptionView.setVisibility(View.VISIBLE); } else { //== Description.PLAIN_TEXT - LinkHelper.createLinksFromPlainText(requireContext(), description.getContent(), + TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(), videoDescriptionView); videoDescriptionView.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 6bcaac63036..b4dd45e9334 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -206,7 +206,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { break; case R.id.menu_item_share: if (currentInfo != null) { - ShareUtils.shareUrl(requireContext(), name, currentInfo.getOriginalUrl()); + ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl()); } break; default: diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 58985e38a3c..85dea83dcee 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -242,7 +242,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { ShareUtils.openUrlInBrowser(requireContext(), url); break; case R.id.menu_item_share: - ShareUtils.shareUrl(requireContext(), name, url); + ShareUtils.shareText(requireContext(), name, url); break; case R.id.menu_item_bookmark: onBookmarkClicked(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index e6cae0c7ebc..fe35a18c404 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -13,15 +13,14 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceManager; - import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.CommentTextOnTouchListener; +import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 3440fbe3c47..1915ff28323 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -1,10 +1,11 @@ package org.schabi.newpipe.info_list.holder; -import androidx.preference.PreferenceManager; import android.text.TextUtils; import android.view.ViewGroup; import android.widget.TextView; +import androidx.preference.PreferenceManager; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index d454da08b8a..84908e41a05 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -4,16 +4,15 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.View; +import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index d5241ae02ff..ee77db89f19 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -33,9 +33,9 @@ import java.util.List; import icepick.State; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; 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 8be281dc3b4..a84745dbf6a 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 @@ -300,7 +300,7 @@ class SubscriptionFragment : BaseStateFragment() { val actions = DialogInterface.OnClickListener { _, i -> when (i) { - 0 -> ShareUtils.shareUrl(requireContext(), selectedItem.name, selectedItem.url) + 0 -> ShareUtils.shareText(requireContext(), selectedItem.name, selectedItem.url) 1 -> deleteChannel(selectedItem) } } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java index e9ae1a1db70..945bc9a044a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -34,8 +34,8 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.App; +import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.util.ThemeHelper; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index 6ea7ecda348..d757a926844 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -46,6 +46,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipe.util.ShareUtils.shareText; public final class PlayQueueActivity extends AppCompatActivity implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, @@ -311,7 +312,7 @@ private void buildItemPopupMenu(final PlayQueueItem item, final View view) { final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3, Menu.NONE, R.string.share); share.setOnMenuItemClickListener(menuItem -> { - shareUrl(item.getTitle(), item.getUrl()); + shareText(getApplicationContext(), item.getTitle(), item.getUrl()); return true; }); @@ -505,18 +506,6 @@ private void openPlaylistAppendDialog(final List playlist) { () -> PlaylistCreationDialog.newInstance(d).show(getSupportFragmentManager(), TAG)); } - //////////////////////////////////////////////////////////////////////////// - // Share - //////////////////////////////////////////////////////////////////////////// - - private void shareUrl(final String subject, final String url) { - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, url); - startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); - } - //////////////////////////////////////////////////////////////////////////// // Binding Service Listener //////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 2453117f3c6..b2b65477abf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3553,7 +3553,7 @@ private void onShareClicked() { && currentMetadata.getMetadata().getServiceId() == YouTube.getServiceId()) { videoUrl += ("&t=" + ts); } - ShareUtils.shareUrl(context, getVideoTitle(), videoUrl); + ShareUtils.shareText(context, getVideoTitle(), videoUrl); } private void onPlayWithKodiClicked() { diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java index 347118de54b..00ce3e616d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java @@ -6,9 +6,12 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import com.google.android.material.bottomsheet.BottomSheetBehavior; + import org.schabi.newpipe.R; import java.util.Arrays; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 253f0fbbad7..a9a36e2f52a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -3,7 +3,6 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import androidx.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.CheckBox; @@ -14,6 +13,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.util.SliderStrategy; diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index 90a979872e9..6131d856545 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import io.reactivex.rxjava3.core.BackpressureStrategy; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.BackpressureStrategy; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.subjects.BehaviorSubject; diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index 0321d197a72..e76af3944da 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -244,7 +244,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { goToReturnActivity(); break; case R.id.menu_item_share_error: - ShareUtils.shareUrl(this, getString(R.string.error_report_title), buildJson()); + ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson()); break; } return false; @@ -267,7 +267,7 @@ private void openPrivacyPolicyDialog(final Context context, final String action) .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) .putExtra(Intent.EXTRA_TEXT, buildJson()); if (i.resolveActivity(getPackageManager()) != null) { - ShareUtils.openContentInApp(context, i); + ShareUtils.openIntentInApp(context, i); } } else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false); diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java index 7a42c9bea3b..b64543d2732 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java @@ -2,12 +2,12 @@ import android.content.SharedPreferences; import android.os.Bundle; -import androidx.preference.PreferenceManager; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.util.ThemeHelper; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 90537564a02..afe42d5d812 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -30,8 +30,8 @@ import java.util.Vector; import de.hdodenhof.circleimageview.CircleImageView; -import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 1fe405552db..e50c858caf1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -15,6 +15,7 @@ import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.content.res.AppCompatResources; @@ -22,7 +23,7 @@ import androidx.core.widget.TextViewCompat; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; -import java.util.List; + import org.schabi.newpipe.R; import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.NotificationConstants; @@ -30,6 +31,8 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; +import java.util.List; + public class NotificationActionsPreference extends Preference { public NotificationActionsPreference(final Context context, final AttributeSet attrs) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java index 02b0c3df5c3..2836fe52b5b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java @@ -2,9 +2,10 @@ import android.content.Context; import android.content.SharedPreferences; -import androidx.preference.PreferenceManager; import android.widget.Toast; +import androidx.preference.PreferenceManager; + import org.schabi.newpipe.R; import java.util.List; diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index 5c7eb4c3f92..d26116139f0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -23,8 +23,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; public class CommentTextOnTouchListener implements View.OnTouchListener { diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 7046b8e49a9..6938cf4cc8b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -365,7 +365,7 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf } } - LinkHelper.createLinksFromHtmlBlock(context, stringBuilder.toString(), + TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance()); metaInfoTextView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java index 27e1f304887..edcb565a04b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; + import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 5f8fb589831..eb3c2182755 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -4,11 +4,10 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; - import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index c235d7d676d..f62f959c47d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -9,10 +9,19 @@ import android.os.Build; import android.text.TextUtils; import android.util.DisplayMetrics; + import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; + +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.units.Decade; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipe.ktx.OffsetDateTimeKt; + import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; @@ -24,12 +33,6 @@ import java.util.Calendar; import java.util.List; import java.util.Locale; -import org.ocpsoft.prettytime.PrettyTime; -import org.ocpsoft.prettytime.units.Decade; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.ListExtractor; -import org.schabi.newpipe.extractor.localization.ContentCountry; -import org.schabi.newpipe.ktx.OffsetDateTimeKt; /* diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 936b4595cb4..4f4fd528360 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -46,9 +45,9 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; +import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -57,6 +56,8 @@ import java.util.ArrayList; +import static org.schabi.newpipe.util.ShareUtils.installApp; + public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; @@ -246,7 +247,7 @@ public static void playOnExternalPlayer(final Context context, final String name public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) { if (intent.resolveActivity(context.getPackageManager()) != null) { - ShareUtils.openContentInApp(context, intent); + ShareUtils.openIntentInApp(context, intent); } else { if (context instanceof Activity) { new AlertDialog.Builder(context) @@ -566,17 +567,6 @@ public static Intent getIntentByLink(final Context context, return getOpenIntent(context, url, service.getServiceId(), linkType); } - private static void installApp(final Context context, final String packageName) { - try { - // Try market:// scheme - ShareUtils.openUrlInBrowser(context, "market://details?id=" + packageName, false); - } catch (final ActivityNotFoundException e) { - // Fall back to google play URL (don't worry F-Droid can handle it :) - ShareUtils.openUrlInBrowser(context, - "https://play.google.com/store/apps/details?id=" + packageName, false); - } - } - /** * Start an activity to install Kore. * diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index e2809579885..dcc39eccf6d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; + import androidx.preference.PreferenceManager; import com.grack.nanojson.JsonArray; diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index a6756991c5c..b38edfeb433 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -2,10 +2,10 @@ import android.content.Context; import android.content.SharedPreferences; -import androidx.preference.PreferenceManager; import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; +import androidx.preference.PreferenceManager; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java index 142b2b20cc5..ffb965ae7dd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java @@ -6,7 +6,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.net.Uri; import android.widget.Toast; @@ -18,6 +17,27 @@ public final class ShareUtils { private ShareUtils() { } + /** + * Open an Intent to install an app. + *

+ * This method will first try open to Google Play Store with the market scheme and falls back to + * Google Play Store web url if this first cannot be found. + * + * @param context the context to use + * @param packageName the package to be installed + */ + public static void installApp(final Context context, final String packageName) { + try { + // Try market:// scheme + openIntentInApp(context, new Intent(Intent.ACTION_VIEW, + Uri.parse("market://details?id=" + packageName))); + } catch (final ActivityNotFoundException e) { + // Fall back to Google Play Store Web URL (don't worry, F-Droid can handle it :)) + openUrlInBrowser(context, + "https://play.google.com/store/apps/details?id=" + packageName, false); + } + } + /** * Open the url with the system default browser. *

@@ -26,12 +46,11 @@ private ShareUtils() { * * @param context the context to use * @param url the url to browse - * @param httpDefaultBrowserTest the boolean to set if the - * test for the default browser will be for HTTP protocol - * or for the created intent + * @param httpDefaultBrowserTest the boolean to set if the test for the default browser will be + * for HTTP protocol or for the created intent */ public static void openUrlInBrowser(final Context context, final String url, - final Boolean httpDefaultBrowserTest) { + final boolean httpDefaultBrowserTest) { final String defaultPackageName; final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -42,14 +61,14 @@ public static void openUrlInBrowser(final Context context, final String url, } if (defaultPackageName.equals("android")) { - // no browser set as default (doesn't work on some devices) + // No browser set as default (doesn't work on some devices) openInDefaultApp(context, intent); } else { try { intent.setPackage(defaultPackageName); context.startActivity(intent); } catch (final ActivityNotFoundException e) { - // not a browser but an app chooser because of OEMs changes + // Not a browser but an app chooser because of OEMs changes intent.setPackage(null); openInDefaultApp(context, intent); } @@ -62,7 +81,7 @@ public static void openUrlInBrowser(final Context context, final String url, * If no browser is set as default, fallbacks to * {@link ShareUtils#openInDefaultApp(Context, Intent)} *

- * This call {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)} with true + * This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true * for the boolean parameter * * @param context the context to use @@ -73,26 +92,29 @@ public static void openUrlInBrowser(final Context context, final String url) { } /** - * Open a content with the system default browser. + * Open an intent with the system default app. + *

+ * The intent can be of every type, excepted a web intent for which + * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used. *

* If no app is set as default, fallbacks to * {@link ShareUtils#openInDefaultApp(Context, Intent)} * * @param context the context to use - * @param intent the intent of the file to open + * @param intent the intent to open */ - public static void openContentInApp(final Context context, final Intent intent) { + public static void openIntentInApp(final Context context, final Intent intent) { final String defaultAppPackageName = getDefaultAppPackageName(context, intent); if (defaultAppPackageName.equals("android")) { - // no app set as default (doesn't work on some devices) + // No app set as default (doesn't work on some devices) openInDefaultApp(context, intent); } else { try { intent.setPackage(defaultAppPackageName); context.startActivity(intent); } catch (final ActivityNotFoundException e) { - // not an app to open a file but an app chooser because of OEMs changes + // Not an app to open the intent but an app chooser because of OEMs changes intent.setPackage(null); openInDefaultApp(context, intent); } @@ -143,9 +165,8 @@ private static String getDefaultAppPackageName(final Context context, final Inte private static String getDefaultBrowserPackageName(final Context context) { final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, - PackageManager.MATCH_DEFAULT_ONLY); - return resolveInfo.activityInfo.packageName; + return context.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName; } /** @@ -155,11 +176,12 @@ private static String getDefaultBrowserPackageName(final Context context) { * @param subject the url subject, typically the title * @param url the url to share */ - public static void shareUrl(final Context context, final String subject, final String url) { + public static void shareText(final Context context, final String subject, final String url) { final Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); shareIntent.putExtra(Intent.EXTRA_TEXT, url); + final Intent intent = new Intent(Intent.ACTION_CHOOSER); intent.putExtra(Intent.EXTRA_INTENT, shareIntent); intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title)); diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java index 153ba7cf48d..73fee32f7f5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java @@ -81,7 +81,7 @@ public enum StreamDialogEntry { }), share(R.string.share, (fragment, item) -> - ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl())); + ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl())); /////////////// diff --git a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java similarity index 92% rename from app/src/main/java/org/schabi/newpipe/util/LinkHelper.java rename to app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java index fba30758fc7..eb729661106 100644 --- a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java @@ -14,15 +14,15 @@ import io.noties.markwon.Markwon; import io.noties.markwon.linkify.LinkifyPlugin; -public final class LinkHelper { - private LinkHelper() { +public final class TextLinkifier { + private TextLinkifier() { } /** * Create web links for contents with an HTML description. *

* This will call - * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. * * @param context the context to use @@ -43,7 +43,7 @@ public static void createLinksFromHtmlBlock(final Context context, * Create web links for contents with a plain text description. *

* This will call - * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and * {@link TextView#setText(CharSequence, TextView.BufferType)}. * @@ -63,7 +63,7 @@ public static void createLinksFromPlainText(final Context context, * Create web links for contents with a markdown description. *

* This will call - * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} * after creating an {@link Markwon} object and using * {@link Markwon#setMarkdown(TextView, String)}. * @@ -84,7 +84,7 @@ public static void createLinksFromMarkdownText(final Context context, *

* Instead of using an ACTION_VIEW intent in the description of a content, this method will * parse the CharSequence and replace all current web links with - * {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)}. + * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. *

* This method is required in order to intercept links and maybe, show a confirmation dialog * before opening a web link. @@ -105,6 +105,7 @@ public void onClick(final View view) { ShareUtils.openUrlInBrowser(context, span.getURL(), false); } }; + textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); textBlockLinked.removeSpan(span); diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index a1af0387acf..5ac4de84ce3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -22,7 +22,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; -import androidx.preference.PreferenceManager; import android.util.TypedValue; import android.view.ContextThemeWrapper; @@ -32,6 +31,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; diff --git a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java index 23e16ff585d..dc667b22a39 100644 --- a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java @@ -2,10 +2,12 @@ import android.content.Context; import android.util.AttributeSet; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; + import com.google.android.material.appbar.CollapsingToolbarLayout; public class CustomCollapsingToolbarLayout extends CollapsingToolbarLayout { diff --git a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java index e7a028d508d..cfa17e20c0b 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java +++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java @@ -4,6 +4,7 @@ import android.os.Build; import android.util.AttributeSet; import android.view.SurfaceView; + import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT; diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index f400b62b157..798d08c729c 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -24,11 +24,12 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; - import android.view.WindowInsets; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import org.schabi.newpipe.R; public final class FocusAwareCoordinator extends CoordinatorLayout { diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index e7719644581..568c3497add 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -24,10 +24,6 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; - -import androidx.core.app.ServiceCompat; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import android.widget.Toast; @@ -37,6 +33,9 @@ import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Builder; +import androidx.core.app.ServiceCompat; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.download.DownloadActivity; diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 79b91cb5782..bea4b6f9420 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -364,7 +364,7 @@ private void viewWithFileProvider(Mission mission) { //mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); if (intent.resolveActivity(mContext.getPackageManager()) != null) { - ShareUtils.openContentInApp(mContext, intent); + ShareUtils.openIntentInApp(mContext, intent); } else { Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show(); } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 9632c4ae072..3270b2b6f72 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -11,7 +11,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; -import androidx.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -21,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 0aca3c4ab13..2ff7ae68d76 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -13,7 +13,7 @@ + lines="280,312"/> Date: Fri, 15 Jan 2021 20:57:19 +0100 Subject: [PATCH 3/3] Move TextLinkifier computation out of main thread --- .../fragments/detail/VideoDetailFragment.java | 31 +++--- .../fragments/list/search/SearchFragment.java | 8 +- .../schabi/newpipe/util/ExtractorHelper.java | 16 +-- .../schabi/newpipe/util/TextLinkifier.java | 100 +++++++++++------- 4 files changed, 92 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 7ebf6639079..9079c47cae6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1229,19 +1229,20 @@ private void prepareDescription(final Description description) { return; } - if (description.getType() == Description.HTML) { - TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(), - videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY); - videoDescriptionView.setVisibility(View.VISIBLE); - } else if (description.getType() == Description.MARKDOWN) { - TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(), - videoDescriptionView); - videoDescriptionView.setVisibility(View.VISIBLE); - } else { - //== Description.PLAIN_TEXT - TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(), - videoDescriptionView); - videoDescriptionView.setVisibility(View.VISIBLE); + switch (description.getType()) { + case Description.HTML: + disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(), + description.getContent(), videoDescriptionView, + HtmlCompat.FROM_HTML_MODE_LEGACY)); + break; + case Description.MARKDOWN: + disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(), + description.getContent(), videoDescriptionView)); + break; + case Description.PLAIN_TEXT: default: + disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(), + description.getContent(), videoDescriptionView)); + break; } } @@ -1557,8 +1558,8 @@ public void handleResult(@NonNull final StreamInfo info) { prepareDescription(info.getDescription()); updateProgressInfo(info); initThumbnailViews(info); - showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator); - + disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, + detailMetaInfoSeparator)); if (player == null || player.isStopped()) { updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 67663a0738a..b15bb97f2d5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -280,8 +280,8 @@ public void onResume() { handleSearchSuggestion(); - showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo), - metaInfoTextView, metaInfoSeparator); + disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo), + metaInfoTextView, metaInfoSeparator)); if (suggestionDisposable == null || suggestionDisposable.isDisposed()) { initSuggestionObserver(); @@ -1002,11 +1002,11 @@ public void handleResult(@NonNull final SearchInfo result) { // List cannot be bundled without creating some containers metaInfo = new MetaInfo[result.getMetaInfo().size()]; metaInfo = result.getMetaInfo().toArray(metaInfo); + disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, + metaInfoSeparator)); handleSearchSuggestion(); - showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator); - lastSearchedString = searchString; nextPage = result.getNextPage(); diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 6938cf4cc8b..6ee69dcd9a4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -22,7 +22,6 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; -import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -68,6 +67,7 @@ import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -325,10 +325,11 @@ public static void handleGeneralException(final Context context, final int servi * @param metaInfos a list of meta information, can be null or empty * @param metaInfoTextView the text view in which to show the formatted HTML * @param metaInfoSeparator another view to be shown or hidden accordingly to the text view + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void showMetaInfoInTextView(@Nullable final List metaInfos, - final TextView metaInfoTextView, - final View metaInfoSeparator) { + public static Disposable showMetaInfoInTextView(@Nullable final List metaInfos, + final TextView metaInfoTextView, + final View metaInfoSeparator) { final Context context = metaInfoTextView.getContext(); final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.show_meta_info_key), true); @@ -336,6 +337,7 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); + return Disposable.empty(); } else { final StringBuilder stringBuilder = new StringBuilder(); @@ -365,11 +367,9 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf } } - TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), - metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); - metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance()); - metaInfoTextView.setVisibility(View.VISIBLE); metaInfoSeparator.setVisibility(View.VISIBLE); + return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), + metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java index eb729661106..08767733339 100644 --- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java +++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java @@ -6,15 +6,23 @@ import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.text.util.Linkify; +import android.util.Log; import android.view.View; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.core.text.HtmlCompat; import io.noties.markwon.Markwon; import io.noties.markwon.linkify.LinkifyPlugin; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; public final class TextLinkifier { + public static final String TAG = TextLinkifier.class.getSimpleName(); + private TextLinkifier() { } @@ -23,20 +31,21 @@ private TextLinkifier() { *

* This will call * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} - * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. + * after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. * * @param context the context to use * @param htmlBlock the htmlBlock to be linked * @param textView the TextView to set the htmlBlock linked * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)} * will be called + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void createLinksFromHtmlBlock(final Context context, - final String htmlBlock, - final TextView textView, - final int htmlCompatFlag) { - changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), - textView); + public static Disposable createLinksFromHtmlBlock(final Context context, + final String htmlBlock, + final TextView textView, + final int htmlCompatFlag) { + return changeIntentsOfDescriptionLinks(context, + HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView); } /** @@ -44,19 +53,20 @@ public static void createLinksFromHtmlBlock(final Context context, *

* This will call * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} - * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and + * after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and * {@link TextView#setText(CharSequence, TextView.BufferType)}. * * @param context the context to use * @param plainTextBlock the block of plain text to be linked * @param textView the TextView to set the plain text block linked + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void createLinksFromPlainText(final Context context, - final String plainTextBlock, - final TextView textView) { + public static Disposable createLinksFromPlainText(final Context context, + final String plainTextBlock, + final TextView textView) { textView.setAutoLinkMask(Linkify.WEB_URLS); textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); - changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + return changeIntentsOfDescriptionLinks(context, textView.getText(), textView); } /** @@ -70,48 +80,66 @@ public static void createLinksFromPlainText(final Context context, * @param context the context to use * @param markdownBlock the block of markdown text to be linked * @param textView the TextView to set the plain text block linked + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void createLinksFromMarkdownText(final Context context, - final String markdownBlock, - final TextView textView) { + public static Disposable createLinksFromMarkdownText(final Context context, + final String markdownBlock, + final TextView textView) { final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build(); markwon.setMarkdown(textView, markdownBlock); - changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + return changeIntentsOfDescriptionLinks(context, textView.getText(), textView); } /** * Change links generated by libraries in the description of a content to a custom link action. *

- * Instead of using an ACTION_VIEW intent in the description of a content, this method will - * parse the CharSequence and replace all current web links with - * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. + * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a + * content, this method will parse the {@link CharSequence} and replace all current web links + * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. *

- * This method is required in order to intercept links and maybe, show a confirmation dialog + * This method is required in order to intercept links and e.g. show a confirmation dialog * before opening a web link. * * @param context the context to use * @param chars the CharSequence to be parsed * @param textView the TextView in which the converted CharSequence will be applied + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - private static void changeIntentsOfDescriptionLinks(final Context context, - final CharSequence chars, - final TextView textView) { - final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); - final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); + private static Disposable changeIntentsOfDescriptionLinks(final Context context, + final CharSequence chars, + final TextView textView) { + return Single.fromCallable(() -> { + final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); + final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); + + for (final URLSpan span : urls) { + final ClickableSpan clickableSpan = new ClickableSpan() { + public void onClick(@NonNull final View view) { + ShareUtils.openUrlInBrowser(context, span.getURL(), false); + } + }; - for (final URLSpan span : urls) { - final ClickableSpan clickableSpan = new ClickableSpan() { - public void onClick(final View view) { - ShareUtils.openUrlInBrowser(context, span.getURL(), false); - } - }; + textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), + textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); + textBlockLinked.removeSpan(span); + } - textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), - textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); - textBlockLinked.removeSpan(span); - } + return textBlockLinked; + }).subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked), + throwable -> { + Log.e(TAG, "Unable to linkify text", throwable); + // this should never happen, but if it does, just fallback to it + setTextViewCharSequence(textView, chars); + }); + } - textView.setText(textBlockLinked); + private static void setTextViewCharSequence(final TextView textView, + final CharSequence charSequence) { + textView.setText(charSequence); textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setVisibility(View.VISIBLE); } }