package ml.docilealligator.infinityforreddit.activities; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.webkit.URLUtil; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.browser.customtabs.CustomTabColorSchemeParams; import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsService; import org.apache.commons.io.FilenameUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; import retrofit2.Retrofit; public class LinkResolverActivity extends AppCompatActivity { public static final String EXTRA_MESSAGE_FULLNAME = "ENF"; public static final String EXTRA_NEW_ACCOUNT_NAME = "ENAN"; public static final String EXTRA_IS_NSFW = "EIN"; private static final String POST_PATTERN = "/r/[\\w-]+/comments/\\w+/?\\w+/?"; private static final String POST_PATTERN_2 = "/(u|U|user)/[\\w-]+/comments/\\w+/?\\w+/?"; private static final String POST_PATTERN_3 = "/[\\w-]+$"; private static final String COMMENT_PATTERN = "/(r|u|U|user)/[\\w-]+/comments/\\w+/?[\\w-]+/\\w+/?"; private static final String SUBREDDIT_PATTERN = "/[rR]/[\\w-]+/?"; private static final String USER_PATTERN = "/(u|U|user)/[\\w-]+/?"; private static final String SHARELINK_SUBREDDIT_PATTERN = "/r/[\\w-]+/s/[\\w-]+"; private static final String SHARELINK_USER_PATTERN = "/u/[\\w-]+/s/[\\w-]+"; private static final String SIDEBAR_PATTERN = "/[rR]/[\\w-]+/about/sidebar"; private static final String MULTIREDDIT_PATTERN = "/user/[\\w-]+/m/\\w+/?"; private static final String MULTIREDDIT_PATTERN_2 = "/[rR]/(\\w+\\+?)+/?"; private static final String REDD_IT_POST_PATTERN = "/\\w+/?"; private static final String REDGIFS_PATTERN = "/watch/[\\w-]+$"; private static final String IMGUR_GALLERY_PATTERN = "/gallery/\\w+/?"; private static final String IMGUR_ALBUM_PATTERN = "/(album|a)/\\w+/?"; private static final String IMGUR_IMAGE_PATTERN = "/\\w+/?"; private static final String MEDIA_PATTERN = "/media/?"; private static final String WIKI_PATTERN = "/[rR]/[\\w-]+/(wiki|w)(?:/[\\w-]+)*"; private static final String GOOGLE_AMP_PATTERN = "/amp/s/amp.reddit.com/.*"; private static final String STREAMABLE_PATTERN = "/\\w+/?"; @Inject @Named("no_oauth") Retrofit mRetrofit; @Inject @Named("default") SharedPreferences mSharedPreferences; @Inject CustomThemeWrapper mCustomThemeWrapper; private Uri getRedditUriByPath(String path) { if (path.charAt(0) != '/') { return Uri.parse("https://www.reddit.com/" + path); } else { return Uri.parse("https://www.reddit.com" + path); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((Infinity) getApplication()).getAppComponent().inject(this); Uri uri = getIntent().getData(); if (uri == null) { String url = getIntent().getStringExtra(Intent.EXTRA_TEXT); if (!URLUtil.isValidUrl(url)) { Toast.makeText(this, R.string.invalid_link, Toast.LENGTH_SHORT).show(); finish(); return; } try { uri = Uri.parse(url); } catch (NullPointerException e) { Toast.makeText(this, R.string.invalid_link, Toast.LENGTH_SHORT).show(); finish(); return; } } if (uri.getScheme() == null && uri.getHost() == null) { if (uri.toString().isEmpty()) { Toast.makeText(this, R.string.invalid_link, Toast.LENGTH_SHORT).show(); finish(); return; } handleUri(getRedditUriByPath(uri.toString())); } else { handleUri(uri); } } private void handleUri(Uri uri) { if (uri == null) { Toast.makeText(this, R.string.no_link_available, Toast.LENGTH_SHORT).show(); } else { String path = uri.getPath(); if (path == null) { deepLinkError(uri); } else { if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } if (path.endsWith(".jpg") || path.endsWith(".png") || path.endsWith(".jpeg")) { Intent intent = new Intent(this, ViewImageOrGifActivity.class); String url = uri.toString(); String fileName = FilenameUtils.getName(path); intent.putExtra(ViewImageOrGifActivity.EXTRA_IMAGE_URL_KEY, url); intent.putExtra(ViewImageOrGifActivity.EXTRA_FILE_NAME_KEY, fileName); intent.putExtra(ViewImageOrGifActivity.EXTRA_POST_TITLE_KEY, fileName); startActivity(intent); } else if (path.endsWith(".gif")) { Intent intent = new Intent(this, ViewImageOrGifActivity.class); String url = uri.toString(); String fileName = FilenameUtils.getName(path); intent.putExtra(ViewImageOrGifActivity.EXTRA_GIF_URL_KEY, url); intent.putExtra(ViewImageOrGifActivity.EXTRA_FILE_NAME_KEY, fileName); intent.putExtra(ViewImageOrGifActivity.EXTRA_POST_TITLE_KEY, fileName); startActivity(intent); } else if (path.endsWith(".mp4")) { Intent intent = new Intent(this, ViewVideoActivity.class); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_TYPE, ViewVideoActivity.VIDEO_TYPE_DIRECT); intent.putExtra(ViewVideoActivity.EXTRA_IS_NSFW, getIntent().getBooleanExtra(EXTRA_IS_NSFW, false)); intent.setData(uri); startActivity(intent); } else { String messageFullname = getIntent().getStringExtra(EXTRA_MESSAGE_FULLNAME); String newAccountName = getIntent().getStringExtra(EXTRA_NEW_ACCOUNT_NAME); String authority = uri.getAuthority(); List segments = uri.getPathSegments(); if (authority != null) { if (authority.equals("reddit-uploaded-media.s3-accelerate.amazonaws.com")) { String unescapedUrl = uri.toString().replace("%2F", "/"); int lastSlashIndex = unescapedUrl.lastIndexOf("/"); if (lastSlashIndex < 0 || lastSlashIndex == unescapedUrl.length() - 1) { deepLinkError(uri); return; } String id = unescapedUrl.substring(lastSlashIndex + 1); Intent intent = new Intent(this, ViewImageOrGifActivity.class); intent.putExtra(ViewImageOrGifActivity.EXTRA_IMAGE_URL_KEY, uri.toString()); intent.putExtra(ViewImageOrGifActivity.EXTRA_FILE_NAME_KEY, id + ".jpg"); startActivity(intent); } else if (authority.equals("v.redd.it")) { Intent intent = new Intent(this, ViewVideoActivity.class); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_TYPE, ViewVideoActivity.VIDEO_TYPE_V_REDD_IT); intent.putExtra(ViewVideoActivity.EXTRA_V_REDD_IT_URL, uri.toString()); startActivity(intent); } else if (authority.contains("reddit.com") || authority.contains("redd.it") || authority.contains("reddit.app")) { if (authority.equals("reddit.app.link") && path.isEmpty()) { String redirect = uri.getQueryParameter("$og_redirect"); if (redirect != null) { handleUri(Uri.parse(redirect)); } else { deepLinkError(uri); } } else if (path.isEmpty()) { Intent intent = new Intent(this, MainActivity.class); startActivity(intent); } else if (path.equals("/report")) { openInWebView(uri); } else if (path.matches(MEDIA_PATTERN)) { // TODO: check if this can be video. // Example: https://www.reddit.com/media?url=https%3A%2F%2Fpreview.redd.it%2F9iamzovpcrta1.png%3Fwidth%3D720%26auto%3Dwebp%26s%3Da6d1287094fc6ad986e1245a5fc3869b2f0c7da4 deepLinkError(uri); } else if (path.matches(POST_PATTERN) || path.matches(POST_PATTERN_2)) { int commentsIndex = segments.lastIndexOf("comments"); if (commentsIndex >= 0 && commentsIndex < segments.size() - 1) { Intent intent = new Intent(this, ViewPostDetailActivity.class); intent.putExtra(ViewPostDetailActivity.EXTRA_POST_ID, segments.get(commentsIndex + 1)); intent.putExtra(ViewPostDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewPostDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else { deepLinkError(uri); } } else if (path.matches(POST_PATTERN_3)) { Intent intent = new Intent(this, ViewPostDetailActivity.class); intent.putExtra(ViewPostDetailActivity.EXTRA_POST_ID, path.substring(1)); intent.putExtra(ViewPostDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewPostDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else if (path.matches(COMMENT_PATTERN)) { int commentsIndex = segments.lastIndexOf("comments"); if (commentsIndex >= 0 && commentsIndex < segments.size() - 1) { Intent intent = new Intent(this, ViewPostDetailActivity.class); intent.putExtra(ViewPostDetailActivity.EXTRA_POST_ID, segments.get(commentsIndex + 1)); intent.putExtra(ViewPostDetailActivity.EXTRA_SINGLE_COMMENT_ID, segments.get(segments.size() - 1)); intent.putExtra(ViewPostDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewPostDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else { deepLinkError(uri); } } else if (path.matches(WIKI_PATTERN)) { String[] pathSegments = path.split("/"); String wikiPage; if (pathSegments.length == 4) { wikiPage = "index"; } else { int lengthThroughWiki = 0; for (int i = 1; i <= 3; ++i) { lengthThroughWiki += pathSegments[i].length() + 1; } wikiPage = path.substring(lengthThroughWiki); } Intent intent = new Intent(this, WikiActivity.class); intent.putExtra(WikiActivity.EXTRA_SUBREDDIT_NAME, segments.get(1)); intent.putExtra(WikiActivity.EXTRA_WIKI_PATH, wikiPage); startActivity(intent); } else if (path.matches(SUBREDDIT_PATTERN)) { Intent intent = new Intent(this, ViewSubredditDetailActivity.class); intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, path.substring(3)); intent.putExtra(ViewSubredditDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewSubredditDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else if (path.matches(USER_PATTERN)) { Intent intent = new Intent(this, ViewUserDetailActivity.class); intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, segments.get(1)); intent.putExtra(ViewUserDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewUserDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else if (path.matches(SIDEBAR_PATTERN)) { Intent intent = new Intent(this, ViewSubredditDetailActivity.class); intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, path.substring(3, path.length() - 14)); intent.putExtra(ViewSubredditDetailActivity.EXTRA_VIEW_SIDEBAR, true); startActivity(intent); } else if (path.matches(MULTIREDDIT_PATTERN)) { Intent intent = new Intent(this, ViewMultiRedditDetailActivity.class); intent.putExtra(ViewMultiRedditDetailActivity.EXTRA_MULTIREDDIT_PATH, path); startActivity(intent); } else if (path.matches(MULTIREDDIT_PATTERN_2)) { String subredditName = path.substring(3); Intent intent = new Intent(this, ViewSubredditDetailActivity.class); intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, subredditName); intent.putExtra(ViewSubredditDetailActivity.EXTRA_MESSAGE_FULLNAME, messageFullname); intent.putExtra(ViewSubredditDetailActivity.EXTRA_NEW_ACCOUNT_NAME, newAccountName); startActivity(intent); } else if (authority.equals("redd.it") && path.matches(REDD_IT_POST_PATTERN)) { Intent intent = new Intent(this, ViewPostDetailActivity.class); intent.putExtra(ViewPostDetailActivity.EXTRA_POST_ID, path.substring(1)); startActivity(intent); } else if (uri.getPath().matches(SHARELINK_SUBREDDIT_PATTERN) || uri.getPath().matches(SHARELINK_USER_PATTERN)) { mRetrofit.callFactory().newCall(new Request.Builder().url(uri.toString()).build()).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.isSuccessful()) { Uri newUri = Uri.parse(response.request().url().toString()); if (newUri.getPath() != null) { if (newUri.getPath().matches(SHARELINK_SUBREDDIT_PATTERN) || newUri.getPath().matches(SHARELINK_USER_PATTERN)) { deepLinkError(newUri); } else { handleUri(newUri); } } else { handleUri(uri); } } else { deepLinkError(uri); } } @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { deepLinkError(uri); } }); } else { deepLinkError(uri); } } else if (authority.equals("click.redditmail.com")) { if (path.startsWith("/CL0/")) { handleUri(Uri.parse(path.substring("/CL0/".length()))); } } else if (authority.contains("redgifs.com")) { if (path.matches(REDGIFS_PATTERN)) { Intent intent = new Intent(this, ViewVideoActivity.class); intent.putExtra(ViewVideoActivity.EXTRA_REDGIFS_ID, path.substring(path.lastIndexOf("/") + 1)); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_TYPE, ViewVideoActivity.VIDEO_TYPE_REDGIFS); intent.putExtra(ViewVideoActivity.EXTRA_IS_NSFW, true); startActivity(intent); } else { deepLinkError(uri); } } else if (authority.contains("imgur.com")) { if (path.matches(IMGUR_GALLERY_PATTERN)) { Intent intent = new Intent(this, ViewImgurMediaActivity.class); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_TYPE, ViewImgurMediaActivity.IMGUR_TYPE_GALLERY); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_ID, segments.get(1)); startActivity(intent); } else if (path.matches(IMGUR_ALBUM_PATTERN)) { Intent intent = new Intent(this, ViewImgurMediaActivity.class); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_TYPE, ViewImgurMediaActivity.IMGUR_TYPE_ALBUM); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_ID, segments.get(1)); startActivity(intent); } else if (path.matches(IMGUR_IMAGE_PATTERN)) { Intent intent = new Intent(this, ViewImgurMediaActivity.class); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_TYPE, ViewImgurMediaActivity.IMGUR_TYPE_IMAGE); intent.putExtra(ViewImgurMediaActivity.EXTRA_IMGUR_ID, path.substring(1)); startActivity(intent); } else if (path.endsWith("gifv") || path.endsWith("mp4")) { String url = uri.toString(); if (path.endsWith("gifv")) { url = url.substring(0, url.length() - 5) + ".mp4"; } Intent intent = new Intent(this, ViewVideoActivity.class); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_TYPE, ViewVideoActivity.VIDEO_TYPE_IMGUR); intent.putExtra(ViewVideoActivity.EXTRA_IS_NSFW, getIntent().getBooleanExtra(EXTRA_IS_NSFW, false)); intent.setData(Uri.parse(url)); startActivity(intent); } else { deepLinkError(uri); } } else if (authority.contains("google.com")) { if (path.matches(GOOGLE_AMP_PATTERN)) { String url = path.substring(11); handleUri(Uri.parse("https://" + url)); } else { deepLinkError(uri); } } else if (authority.equals("streamable.com")) { if (path.matches(STREAMABLE_PATTERN)) { String shortCode = segments.get(0); Intent intent = new Intent(this, ViewVideoActivity.class); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_TYPE, ViewVideoActivity.VIDEO_TYPE_STREAMABLE); intent.putExtra(ViewVideoActivity.EXTRA_STREAMABLE_SHORT_CODE, shortCode); startActivity(intent); } else { deepLinkError(uri); } } else { deepLinkError(uri); } } else { deepLinkError(uri); } } } } finish(); } private void deepLinkError(Uri uri) { PackageManager pm = getPackageManager(); String authority = uri.getAuthority(); if(authority != null && (authority.contains("reddit.com") || authority.contains("redd.it") || authority.contains("reddit.app.link"))) { openInCustomTabs(uri, pm, false); return; } int linkHandler = Integer.parseInt(mSharedPreferences.getString(SharedPreferencesUtils.LINK_HANDLER, "0")); if (linkHandler == 0) { openInBrowser(uri, pm, true); } else if (linkHandler == 1) { openInCustomTabs(uri, pm, true); } else { openInWebView(uri); } } private void openInBrowser(Uri uri, PackageManager pm, boolean handleError) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(uri); try { startActivity(intent); } catch (ActivityNotFoundException e) { if (handleError) { openInCustomTabs(uri, pm, false); } else { openInWebView(uri); } } } private ArrayList getCustomTabsPackages(PackageManager pm) { // Get default VIEW intent handler. Intent activityIntent = new Intent() .setAction(Intent.ACTION_VIEW) .addCategory(Intent.CATEGORY_BROWSABLE) .setData(Uri.fromParts("http", "", null)); // Get all apps that can handle VIEW intents. List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); ArrayList packagesSupportingCustomTabs = new ArrayList<>(); for (ResolveInfo info : resolvedActivityList) { Intent serviceIntent = new Intent(); serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); serviceIntent.setPackage(info.activityInfo.packageName); // Check if this package also resolves the Custom Tabs service. if (pm.resolveService(serviceIntent, 0) != null) { packagesSupportingCustomTabs.add(info); } } return packagesSupportingCustomTabs; } private void openInCustomTabs(Uri uri, PackageManager pm, boolean handleError) { ArrayList resolveInfos = getCustomTabsPackages(pm); if (!resolveInfos.isEmpty()) { CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); // add share action to menu list builder.setShareState(CustomTabsIntent.SHARE_STATE_ON); builder.setDefaultColorSchemeParams( new CustomTabColorSchemeParams.Builder() .setToolbarColor(mCustomThemeWrapper.getColorPrimary()) .build()); CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.intent.setPackage(resolveInfos.get(0).activityInfo.packageName); if (uri.getScheme() == null) { uri = Uri.parse("http://" + uri); } try { customTabsIntent.launchUrl(this, uri); } catch (ActivityNotFoundException e) { if (handleError) { openInBrowser(uri, pm, false); } else { openInWebView(uri); } } } else { if (handleError) { openInBrowser(uri, pm, false); } else { openInWebView(uri); } } } private void openInWebView(Uri uri) { Intent intent = new Intent(this, WebViewActivity.class); intent.setData(uri); startActivity(intent); } }