Correctly open urls in browser on Android 11+
- Fix misconfiguration in manifest ('http|https|market' is not valid)
- Split ShareUtils functions taking a boolean parameter into pairs of functions with better names and less runtime checks
- Move all Kore-related functions to KoreUtils
- Remove the toast_no_player string
This commit is contained in:
parent
a17c32fada
commit
de067fe798
83 changed files with 125 additions and 267 deletions
|
|
@ -1,6 +1,6 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp;
|
||||
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
|
|
@ -50,9 +50,9 @@ import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
|||
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.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayQueueActivity;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayerType;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||
|
|
@ -63,8 +63,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
|
||||
|
||||
public final class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
|
||||
|
|
@ -323,15 +321,13 @@ public final class NavigationHelper {
|
|||
|
||||
public static void resolveActivityOrAskToInstall(@NonNull final Context context,
|
||||
@NonNull final Intent intent) {
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
ShareUtils.openIntentInApp(context, intent, false);
|
||||
} else {
|
||||
if (!ShareUtils.tryOpenIntentInApp(context, intent)) {
|
||||
if (context instanceof Activity) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(R.string.no_player_found)
|
||||
.setPositiveButton(R.string.install,
|
||||
(dialog, which) -> ShareUtils.openUrlInBrowser(context,
|
||||
context.getString(R.string.fdroid_vlc_url), false))
|
||||
(dialog, which) -> ShareUtils.installApp(context,
|
||||
context.getString(R.string.vlc_package)))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which)
|
||||
-> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
|
||||
.show();
|
||||
|
|
@ -684,34 +680,6 @@ public final class NavigationHelper {
|
|||
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an activity to install Kore.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void installKore(final Context context) {
|
||||
installApp(context, context.getString(R.string.kore_package));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Kore app to show a video on Kodi.
|
||||
* <p>
|
||||
* For a list of supported urls see the
|
||||
* <a href="https://github.com/xbmc/Kore/blob/master/app/src/main/AndroidManifest.xml">
|
||||
* Kore source code
|
||||
* </a>.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param videoURL the url to the video
|
||||
*/
|
||||
public static void playWithKore(final Context context, final Uri videoURL) {
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(context.getString(R.string.kore_package));
|
||||
intent.setData(videoURL);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish this <code>Activity</code> as well as all <code>Activities</code> running below it
|
||||
* and then start <code>MainActivity</code>.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package org.schabi.newpipe.util.external_communication;
|
||||
|
||||
import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp;
|
||||
import static org.schabi.newpipe.util.external_communication.ShareUtils.tryOpenIntentInApp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
|
@ -8,7 +13,6 @@ import androidx.preference.PreferenceManager;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
/**
|
||||
* Util class that provides methods which are related to the Kodi Media Center and its Kore app.
|
||||
|
|
@ -29,13 +33,39 @@ public final class KoreUtils {
|
|||
.getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
|
||||
}
|
||||
|
||||
public static void showInstallKoreDialog(@NonNull final Context context) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install, (dialog, which) ->
|
||||
NavigationHelper.installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
});
|
||||
builder.create().show();
|
||||
/**
|
||||
* Start an activity to install Kore.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void installKore(final Context context) {
|
||||
installApp(context, context.getString(R.string.kore_package));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Kore app to show a video on Kodi, and if the app is not installed ask the user to
|
||||
* install it.
|
||||
* <p>
|
||||
* For a list of supported urls see the
|
||||
* <a href="https://github.com/xbmc/Kore/blob/master/app/src/main/AndroidManifest.xml">
|
||||
* Kore source code
|
||||
* </a>.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param videoURL the url to the video
|
||||
*/
|
||||
public static void playWithKore(final Context context, final Uri videoURL) {
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||
.setPackage(context.getString(R.string.kore_package))
|
||||
.setData(videoURL)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
if (!tryOpenIntentInApp(context, intent)) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install, (dialog, which) -> installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> { });
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,60 +41,56 @@ public final class ShareUtils {
|
|||
* second param (a system chooser will be opened if there are multiple markets and no default)
|
||||
* and falls back to Google Play Store web URL if no app to handle the market scheme was found.
|
||||
* <p>
|
||||
* It uses {@link #openIntentInApp(Context, Intent, boolean)} to open market scheme
|
||||
* and {@link #openUrlInBrowser(Context, String, boolean)} to open Google Play Store
|
||||
* web URL with false for the boolean param.
|
||||
* It uses {@link #openIntentInApp(Context, Intent)} to open market scheme and {@link
|
||||
* #openUrlInBrowser(Context, String)} to open Google Play Store web URL.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param packageId the package id of the app to be installed
|
||||
*/
|
||||
public static void installApp(@NonNull final Context context, final String packageId) {
|
||||
// Try market scheme
|
||||
final boolean marketSchemeResult = openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
|
||||
final Intent marketSchemeIntent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=" + packageId))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), false);
|
||||
if (!marketSchemeResult) {
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (!tryOpenIntentInApp(context, marketSchemeIntent)) {
|
||||
// Fall back to Google Play Store Web URL (F-Droid can handle it)
|
||||
openUrlInBrowser(context,
|
||||
"https://play.google.com/store/apps/details?id=" + packageId, false);
|
||||
openUrlInApp(context, "https://play.google.com/store/apps/details?id=" + packageId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the url with the system default browser.
|
||||
* <p>
|
||||
* If no browser is set as default, fallbacks to
|
||||
* {@link #openAppChooser(Context, Intent, boolean)}
|
||||
* Open the url with the system default browser. If no browser is set as default, falls back to
|
||||
* {@link #openAppChooser(Context, Intent, boolean)}. This function selects the package to open
|
||||
* based on which apps respond to the {@code http://} schema alone, which should exclude special
|
||||
* non-browser apps that are can handle the url (e.g. the official YouTube app). Therefore
|
||||
* please <b>prefer {@link #openUrlInApp(Context, String)}</b>, that handles package resolution
|
||||
* in a standard way, unless this is the action of an explicit "Open in browser" button.
|
||||
*
|
||||
* @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
|
||||
* @return true if the URL can be opened or false if it cannot
|
||||
*/
|
||||
public static boolean openUrlInBrowser(@NonNull final Context context,
|
||||
final String url,
|
||||
final boolean httpDefaultBrowserTest) {
|
||||
final String defaultPackageName;
|
||||
* @param context the context to use
|
||||
* @param url the url to browse
|
||||
**/
|
||||
public static void openUrlInBrowser(@NonNull final Context context, final String url) {
|
||||
// Resolve using a generic http://, so we are sure to get a browser and not e.g. the yt app.
|
||||
// Note that this requires the `http` schema to be added to `<queries>` in the manifest.
|
||||
final ResolveInfo defaultBrowserInfo = context.getPackageManager().resolveActivity(
|
||||
new Intent(Intent.ACTION_VIEW, Uri.parse("http://")),
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (defaultBrowserInfo == null) {
|
||||
// No app installed to open a web url
|
||||
Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final String defaultBrowserPackage = defaultBrowserInfo.activityInfo.packageName;
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
if (httpDefaultBrowserTest) {
|
||||
defaultPackageName = getDefaultAppPackageName(context, new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("http://")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} else {
|
||||
defaultPackageName = getDefaultAppPackageName(context, intent);
|
||||
}
|
||||
|
||||
if (defaultPackageName.equals("android")) {
|
||||
if (defaultBrowserPackage.equals("android")) {
|
||||
// No browser set as default (doesn't work on some devices)
|
||||
openAppChooser(context, intent, true);
|
||||
} else {
|
||||
try {
|
||||
// will be empty on Android 12+
|
||||
if (!defaultPackageName.isEmpty()) {
|
||||
intent.setPackage(defaultPackageName);
|
||||
}
|
||||
intent.setPackage(defaultBrowserPackage);
|
||||
context.startActivity(intent);
|
||||
} catch (final ActivityNotFoundException e) {
|
||||
// Not a browser but an app chooser because of OEMs changes
|
||||
|
|
@ -102,61 +98,54 @@ public final class ShareUtils {
|
|||
openAppChooser(context, intent, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the url with the system default browser.
|
||||
* <p>
|
||||
* If no browser is set as default, fallbacks to
|
||||
* {@link #openAppChooser(Context, Intent, boolean)}
|
||||
* <p>
|
||||
* This calls {@link #openUrlInBrowser(Context, String, boolean)} with true
|
||||
* for the boolean parameter
|
||||
* Open a url with the system default app using {@link Intent#ACTION_VIEW}, showing a toast in
|
||||
* case of failure.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param url the url to browse
|
||||
* @return true if the URL can be opened or false if it cannot be
|
||||
**/
|
||||
public static boolean openUrlInBrowser(@NonNull final Context context, final String url) {
|
||||
return openUrlInBrowser(context, url, true);
|
||||
* @param url the url to open
|
||||
*/
|
||||
public static void openUrlInApp(@NonNull final Context context, final String url) {
|
||||
openIntentInApp(context, new Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an intent with the system default app.
|
||||
* <p>
|
||||
* The intent can be of every type, excepted a web intent for which
|
||||
* {@link #openUrlInBrowser(Context, String, boolean)} should be used.
|
||||
* <p>
|
||||
* If no app can open the intent, a toast with the message {@code No app on your device can
|
||||
* open this} is shown.
|
||||
* Open an intent with the system default app. Use {@link #openIntentInApp(Context, Intent)} to
|
||||
* show a toast in case of failure.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param intent the intent to open
|
||||
* @param showToast a boolean to set if a toast is displayed to user when no app is installed
|
||||
* to open the intent (true) or not (false)
|
||||
* @return true if the intent can be opened or false if it cannot be
|
||||
* @param context the context to use
|
||||
* @param intent the intent to open
|
||||
* @return true if the intent could be opened successfully, false otherwise
|
||||
*/
|
||||
public static boolean openIntentInApp(@NonNull final Context context,
|
||||
@NonNull final Intent intent,
|
||||
final boolean showToast) {
|
||||
final String defaultPackageName = getDefaultAppPackageName(context, intent);
|
||||
|
||||
if (defaultPackageName.isEmpty()) {
|
||||
// No app installed to open the intent
|
||||
if (showToast) {
|
||||
Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
public static boolean tryOpenIntentInApp(@NonNull final Context context,
|
||||
@NonNull final Intent intent) {
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (final ActivityNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an intent with the system default app, showing a toast in case of failure. Use {@link
|
||||
* #tryOpenIntentInApp(Context, Intent)} if you don't want the toast. Use {@link
|
||||
* #openUrlInApp(Context, String)} as a shorthand for {@link Intent#ACTION_VIEW} with urls.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param intent the intent to
|
||||
*/
|
||||
public static void openIntentInApp(@NonNull final Context context,
|
||||
@NonNull final Intent intent) {
|
||||
if (!tryOpenIntentInApp(context, intent)) {
|
||||
Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the system chooser to launch an intent.
|
||||
* <p>
|
||||
|
|
@ -206,31 +195,6 @@ public final class ShareUtils {
|
|||
context.startActivity(chooserIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default app package name.
|
||||
* <p>
|
||||
* If no app is set as default, it will return "android" (not on some devices because some
|
||||
* OEMs changed the app chooser).
|
||||
* <p>
|
||||
* If no app is installed on user's device to handle the intent, it will return an empty string.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param intent the intent to get default app
|
||||
* @return the package name of the default app, an empty string if there's no app installed to
|
||||
* handle the intent or the app chooser if there's no default
|
||||
*/
|
||||
private static String getDefaultAppPackageName(@NonNull final Context context,
|
||||
@NonNull final Intent intent) {
|
||||
final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
||||
if (resolveInfo == null) {
|
||||
return "";
|
||||
} else {
|
||||
return resolveInfo.activityInfo.packageName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the android share sheet to share a content.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ final class UrlLongPressClickableSpan extends LongPressClickableSpan {
|
|||
public void onClick(@NonNull final View view) {
|
||||
if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(
|
||||
disposables, context, url)) {
|
||||
ShareUtils.openUrlInBrowser(context, url, false);
|
||||
ShareUtils.openUrlInApp(context, url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue