tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit dc64c491ba96139e8cce50351d1a1af7c5aa60aa
parent 25e00bcbfbc76566578e525c0729ff8b5c5a65dd
Author: Alex Catarineu <acat@torproject.org>
Date:   Tue, 22 Sep 2020 16:34:51 +0200

TB 34378: [android] Port external helper app prompting

Together with the corresponding fenix patch, this allows all `startActivity`
that may open external apps to be replaced by `TorUtils.startActivityPrompt`.

Originally, android-components#40007 and fenix#34378.

Diffstat:
Mmobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksFeature.kt | 2+-
Mmobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt | 3++-
Mmobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt | 4+++-
Mmobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/AbstractFetchDownloadService.kt | 3++-
Mmobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt | 18+++++++++---------
Amobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/TorUtils.kt | 26++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareController.kt | 3++-
8 files changed, 94 insertions(+), 14 deletions(-)

diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksFeature.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksFeature.kt @@ -154,7 +154,7 @@ class AppLinksFeature( isAuthenticationFlow: Boolean, fragmentManager: FragmentManager?, ): Boolean { - val shouldShowPrompt = isPrivate || isWallet || shouldPrompt() + val shouldShowPrompt = true return fragmentManager == null || !shouldShowPrompt || isAuthenticationFlow } diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt @@ -22,6 +22,7 @@ import mozilla.components.support.utils.Browsers import mozilla.components.support.utils.BrowsersCache import mozilla.components.support.utils.ext.packageManagerCompatHelper import java.net.URISyntaxException +import mozilla.components.support.utils.TorUtils @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal const val EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url" @@ -256,7 +257,7 @@ class AppLinksUseCases( if (launchInNewTask) { it.flags = it.flags or Intent.FLAG_ACTIVITY_NEW_TASK } - context.startActivity(it) + TorUtils.startActivityPrompt(context, it) } catch (e: Exception) { when (e) { is ActivityNotFoundException, is SecurityException, is NullPointerException -> { diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt @@ -28,6 +28,7 @@ import mozilla.components.support.ktx.kotlin.stripMailToProtocol import mozilla.components.support.ktx.kotlin.takeOrReplace import mozilla.components.ui.widgets.DefaultSnackbarDelegate import mozilla.components.ui.widgets.SnackbarDelegate +import mozilla.components.support.utils.TorUtils /** * A candidate for an item to be displayed in the context menu. @@ -498,7 +499,8 @@ data class ContextMenuCandidate( } try { - context.startActivity( + TorUtils.startActivityPrompt( + context, intent.createChooserExcludingCurrentApp( context, context.getString(R.string.mozac_feature_contextmenu_share_link), diff --git a/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/AbstractFetchDownloadService.kt b/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/AbstractFetchDownloadService.kt @@ -77,6 +77,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.throttleLatest import mozilla.components.support.utils.DownloadUtils import mozilla.components.support.utils.ext.registerReceiverCompat import mozilla.components.support.utils.ext.stopForegroundCompat +import mozilla.components.support.utils.TorUtils import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -997,7 +998,7 @@ abstract class AbstractFetchDownloadService : Service() { ) return try { - applicationContext.startActivity(newIntent) + TorUtils.startActivityPrompt(applicationContext, newIntent) true } catch (_: ActivityNotFoundException) { false diff --git a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt @@ -48,6 +48,7 @@ import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.R import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.utils.ext.packageManagerCompatHelper +import mozilla.components.support.utils.TorUtils import java.io.File /** @@ -116,12 +117,11 @@ fun Context.share(text: String, subject: String = getString(R.string.mozac_suppo flags = FLAG_ACTIVITY_NEW_TASK } - startActivity( - intent.createChooserExcludingCurrentApp( - this, - getString(R.string.mozac_support_ktx_menu_share_with), - ), - ) + val shareIntent = intent.createChooserExcludingCurrentApp(this, getString(R.string.mozac_support_ktx_menu_share_with)).apply { + flags = FLAG_ACTIVITY_NEW_TASK + } + + TorUtils.startActivityPrompt(this, shareIntent) true } catch (e: ActivityNotFoundException) { Log.log(Log.Priority.WARN, message = "No activity to share to found", throwable = e, tag = "share") @@ -261,7 +261,7 @@ fun Context.email( flags = FLAG_ACTIVITY_NEW_TASK } - startActivity(emailIntent) + TorUtils.startActivityPrompt(this, emailIntent) true } catch (e: ActivityNotFoundException) { Logger.warn("No activity found to handle email intent", throwable = e) @@ -292,7 +292,7 @@ fun Context.call( flags = FLAG_ACTIVITY_NEW_TASK } - startActivity(callIntent) + TorUtils.startActivityPrompt(this, callIntent) true } catch (e: ActivityNotFoundException) { Logger.warn("No activity found to handle dial intent", throwable = e) @@ -320,7 +320,7 @@ fun Context.addContact( addFlags(FLAG_ACTIVITY_NEW_TASK) } - startActivity(intent) + TorUtils.startActivityPrompt(this, intent) true } catch (e: ActivityNotFoundException) { Logger.warn("No activity found to handle dial intent", throwable = e) diff --git a/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/TorUtils.kt b/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/TorUtils.kt @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.utils + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent + +object TorUtils { + const val TORBROWSER_START_ACTIVITY_PROMPT = "torbrowser_start_activity_prompt" + + // Delegates showing prompt and possibly starting the activity to the main app activity. + // Highly dependant on Fenix/Tor Browser for Android. + // One downside of this implementation is that it does not throw exceptions like the + // direct context.startActivity, so the UI will behave as if the startActivity call was + // done successfully, which may not always be the case. + fun startActivityPrompt(context: Context, intent: Intent) { + val intentContainer = Intent() + intentContainer.setPackage(context.applicationContext.packageName) + intentContainer.putExtra(TORBROWSER_START_ACTIVITY_PROMPT, PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)) + intentContainer.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intentContainer) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix import android.app.assist.AssistContent +import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent @@ -60,6 +61,9 @@ import mozilla.components.browser.state.state.WebExtensionState import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineView import mozilla.components.concept.storage.HistoryMetadataKey +import mozilla.components.feature.app.links.R as appLinksR +import mozilla.components.feature.app.links.RedirectDialogFragment +import mozilla.components.feature.app.links.SimpleRedirectDialogFragment import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate import mozilla.components.feature.customtabs.isCustomTabIntent import mozilla.components.feature.media.ext.findActiveMediaTab @@ -79,6 +83,7 @@ import mozilla.components.support.utils.BootUtils import mozilla.components.support.utils.BrowsersCache import mozilla.components.support.utils.BuildManufacturerChecker import mozilla.components.support.utils.SafeIntent +import mozilla.components.support.utils.TorUtils import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.webextensions.WebExtensionPopupObserver import mozilla.telemetry.glean.private.NoExtras @@ -358,6 +363,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } } + private var dialog: RedirectDialogFragment? = null + @Suppress("CognitiveComplexMethod", "CyclomaticComplexMethod") final override fun onCreate(savedInstanceState: Bundle?) { // DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL. @@ -894,6 +901,28 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { super.recreate() } + // Copied from mozac AppLinksFeature.kt + internal fun getOrCreateDialog(): RedirectDialogFragment { + val existingDialog = dialog + if (existingDialog != null) { + return existingDialog + } + + SimpleRedirectDialogFragment.newInstance( + getString(appLinksR.string.mozac_feature_applinks_normal_confirm_dialog_title), + ).also { + dialog = it + return it + } + } + private fun isAlreadyADialogCreated(): Boolean { + return findPreviousDialogFragment() != null + } + + private fun findPreviousDialogFragment(): RedirectDialogFragment? { + return supportFragmentManager.findFragmentByTag(RedirectDialogFragment.FRAGMENT_TAG) as? RedirectDialogFragment + } + /** * Handles intents received when the activity is open. */ @@ -909,6 +938,26 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { return } + val startIntent = intent.getParcelableExtraCompat(TorUtils.TORBROWSER_START_ACTIVITY_PROMPT, PendingIntent::class.java) + if (startIntent != null) { + if (startIntent.creatorPackage == applicationContext.packageName) { + val dialog = getOrCreateDialog() + dialog.onConfirmRedirect = { + @Suppress("EmptyCatchBlock") + try { + startIntent.send() + } catch (error: PendingIntent.CanceledException) { + } + } + dialog.onCancelRedirect = {} + + if (!isAlreadyADialogCreated()) { + dialog.showNow(supportFragmentManager, RedirectDialogFragment.FRAGMENT_TAG) + } + } + return + } + // Diagnostic breadcrumb for "Display already aquired" crash: // https://github.com/mozilla-mobile/android-components/issues/7960 breadcrumb( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -37,6 +37,7 @@ import mozilla.components.support.ktx.kotlin.isExtensionUrl import mozilla.telemetry.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.SyncAccount +import mozilla.components.support.utils.TorUtils import org.mozilla.fenix.R import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint @@ -155,7 +156,7 @@ class DefaultShareController( @Suppress("TooGenericExceptionCaught") val result = try { - context.startActivity(intent) + TorUtils.startActivityPrompt(context, intent) ShareController.Result.SUCCESS } catch (e: Exception) { when (e) {