[tbb-commits] [Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] fixup! Add Tor integration and UI

Dan Ballard (@dan) git at gitlab.torproject.org
Fri Jan 26 06:06:01 UTC 2024



Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android


Commits:
6d0a6453 by Dan Ballard at 2024-01-25T16:15:45-08:00
fixup! Add Tor integration and UI

Bug 42252: Make TorController and interface and add a Geckoview implementation

- - - - -


4 changed files:

- fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt


Changes:

=====================================
fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
=====================================
@@ -43,7 +43,8 @@ import org.mozilla.fenix.perf.StartupActivityLog
 import org.mozilla.fenix.perf.StartupStateProvider
 import org.mozilla.fenix.perf.StrictModeManager
 import org.mozilla.fenix.perf.lazyMonitored
-import org.mozilla.fenix.tor.TorController
+import org.mozilla.fenix.tor.TorControllerGV
+import org.mozilla.fenix.tor.TorControllerTAS
 import org.mozilla.fenix.utils.ClipboardHandler
 import org.mozilla.fenix.utils.Settings
 import org.mozilla.fenix.wifi.WifiConnectionMonitor
@@ -201,7 +202,7 @@ class Components(private val context: Context) {
             ),
         )
     }
-    val torController by lazyMonitored { TorController(context) }
+    val torController by lazyMonitored { if (settings.useNewBootstrap) TorControllerGV(context) else TorControllerTAS(context) }
 }
 
 /**


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
=====================================
@@ -4,22 +4,7 @@
 
 package org.mozilla.fenix.tor
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import androidx.lifecycle.LifecycleCoroutineScope
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withTimeoutOrNull
-
-import org.mozilla.fenix.BuildConfig
-
-import org.torproject.android.service.TorService
-import org.torproject.android.service.TorServiceConstants
-import org.torproject.android.service.util.Prefs
 
 interface TorEvents {
     fun onTorConnecting()
@@ -28,347 +13,59 @@ interface TorEvents {
     fun onTorStopped()
 }
 
-private enum class TorStatus {
-    OFF,
-    STARTING,
-    ON,
-    STOPPING,
-    UNKNOWN;
+internal enum class TorStatus(val status: String) {
+    OFF("OFF"),
+    STARTING("STARTING"),
+    ON("ON"),
+    STOPPING("STOPPING"),
+    UNKNOWN("UNKNOWN");
 
-    fun getStateFromString(status: String): TorStatus {
-        return when (status) {
-            TorServiceConstants.STATUS_ON -> ON
-            TorServiceConstants.STATUS_STARTING -> STARTING
-            TorServiceConstants.STATUS_STOPPING -> STOPPING
-            TorServiceConstants.STATUS_OFF -> OFF
-            else -> UNKNOWN
+    companion object {
+        fun fromString(status: String): TorStatus {
+            return when (status) {
+                "ON" -> ON
+                "STARTING" -> STARTING
+                "STOPPING" -> STOPPING
+                "OFF" -> OFF
+                else -> UNKNOWN
+            }
         }
     }
 
     fun isOff() = this == OFF
     fun isOn() = this == ON
     fun isStarting() = this == STARTING
-    fun isStarted() = ((this == TorStatus.STARTING) || (this == TorStatus.ON))
+    fun isStarted() = ((this == STARTING) || (this == ON))
     fun isStopping() = this == STOPPING
     fun isUnknown() = this == UNKNOWN
 }
 
- at SuppressWarnings("TooManyFunctions")
-class TorController(
-    private val context: Context
-) : TorEvents {
-
-    private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
-    private val entries = mutableListOf<Pair<String?, String?>>()
-    val logEntries get() = entries
-
-    private var torListeners = mutableListOf<TorEvents>()
-
-    private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
-    private var lockTorListenersMutation = false
-
-    private var lastKnownStatus = TorStatus.OFF
-    private var wasTorBootstrapped = false
-    private var isTorRestarting = false
-
-    // This may be a lie
-    private var isTorBootstrapped = false
-        get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
-
-    val isDebugLoggingEnabled get() =
-        context
-        .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
-        .getBoolean("pref_enable_logging", false)
-
-    val isStarting get() = lastKnownStatus.isStarting()
-    val isRestarting get() = isTorRestarting
-    val isBootstrapped get() = isTorBootstrapped
-    val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
-
+interface TorController: TorEvents {
+    val logEntries: MutableList<Pair<String?, String?>>
+    val isStarting: Boolean
+    val isRestarting: Boolean
+    val isBootstrapped: Boolean
+    val isConnected: Boolean
     var bridgesEnabled: Boolean
-        get() = Prefs.bridgesEnabled()
-        set(value) { Prefs.putBridgesEnabled(value) }
-
     var bridgeTransport: TorBridgeTransportConfig
-        get() {
-            return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
-                Prefs.getBridgesList()
-            )
-        }
-        set(value) {
-            if (value == TorBridgeTransportConfig.USER_PROVIDED) {
-                // Don't set the pref when the value is USER_PROVIDED because
-                // "user_provided" is not a valid bridge or transport type.
-                // This call should be followed by setting userProvidedBridges.
-                return
-            }
-            Prefs.setBridgesList(value.transportName)
-        }
-
     var userProvidedBridges: String?
-        get() {
-            val bridges = Prefs.getBridgesList()
-            val bridgeType =
-                TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
-            return when (bridgeType) {
-                TorBridgeTransportConfig.USER_PROVIDED -> bridges
-                else -> null
-            }
-        }
-        set(value) {
-            Prefs.setBridgesList(value)
-        }
-
-    fun start() {
-        // Register receiver
-        lbm.registerReceiver(
-            persistentBroadcastReceiver,
-            IntentFilter(TorServiceConstants.ACTION_STATUS)
-        )
-        lbm.registerReceiver(
-            persistentBroadcastReceiver,
-            IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
-        )
-    }
 
-    fun stop() {
-        lbm.unregisterReceiver(persistentBroadcastReceiver)
-    }
+    fun start()
+    fun stop()
 
-    private val persistentBroadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intent.action == null ||
-                (intent.action != TorServiceConstants.ACTION_STATUS &&
-                intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
-            ) {
-                    return
-            }
-            val action = intent.action
+    override fun onTorConnecting()
+    override fun onTorConnected()
+    override fun onTorStatusUpdate(entry: String?, status: String?)
+    override fun onTorStopped()
 
-            val logentry: String?
-            val status: String?
-            if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
-                logentry = intent.getExtras()
-                    ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
-            } else {
-                logentry = null
-            }
-
-            status = intent.getExtras()
-                ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
-
-            if (logentry == null && status == null) {
-                return
-            }
-
-            onTorStatusUpdate(logentry, status)
-
-            if (status == null) {
-                return
-            }
-
-            val newStatus = lastKnownStatus.getStateFromString(status)
-
-            if (newStatus.isUnknown() && wasTorBootstrapped) {
-                stopTor()
-            }
-
-            entries.add(Pair(logentry, status))
-
-            if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
-                wasTorBootstrapped = true
-                onTorConnected()
-            }
-
-            if (lastKnownStatus.isStopping() && newStatus.isOff()) {
-                if (isTorRestarting) {
-                    initiateTorBootstrap()
-                } else {
-                    onTorStopped()
-                }
-            }
-
-            if (lastKnownStatus.isOff() && newStatus.isStarting()) {
-                isTorRestarting = false
-            }
-
-            lastKnownStatus = newStatus
-        }
-    }
-
-    override fun onTorConnecting() {
-        lockTorListenersMutation = true
-        torListeners.forEach { it.onTorConnecting() }
-        lockTorListenersMutation = false
-
-        handlePendingRegistrationChanges()
-    }
-
-    override fun onTorConnected() {
-        lockTorListenersMutation = true
-        torListeners.forEach { it.onTorConnected() }
-        lockTorListenersMutation = false
-
-        handlePendingRegistrationChanges()
-    }
+    fun registerTorListener(l: TorEvents)
+    fun unregisterTorListener(l: TorEvents)
 
-    override fun onTorStatusUpdate(entry: String?, status: String?) {
-        lockTorListenersMutation = true
-        torListeners.forEach { it.onTorStatusUpdate(entry, status) }
-        lockTorListenersMutation = false
-
-        handlePendingRegistrationChanges()
-    }
-
-    override fun onTorStopped() {
-        lockTorListenersMutation = true
-        torListeners.forEach { it.onTorStopped() }
-        lockTorListenersMutation = false
-
-        handlePendingRegistrationChanges()
-    }
-
-    fun registerTorListener(l: TorEvents) {
-        if (torListeners.contains(l)) {
-            return
-        }
-
-        if (lockTorListenersMutation) {
-            pendingRegisterChangeList.add(Pair(l, true))
-        } else {
-            torListeners.add(l)
-        }
-    }
-
-    fun unregisterTorListener(l: TorEvents) {
-        if (!torListeners.contains(l)) {
-            return
-        }
-
-        if (lockTorListenersMutation) {
-            pendingRegisterChangeList.add(Pair(l, false))
-        } else {
-            torListeners.remove(l)
-        }
-    }
-
-    private fun handlePendingRegistrationChanges() {
-        pendingRegisterChangeList.forEach {
-            if (it.second) {
-                registerTorListener(it.first)
-            } else {
-                unregisterTorListener(it.first)
-            }
-        }
-
-        pendingRegisterChangeList.clear()
-    }
-
-    /**
-     * Receive the current Tor status.
-     *
-     * Send a request for the current status and receive the response.
-     * Returns true if Tor is running, false otherwise.
-     *
-     */
-    private suspend fun checkTorIsStarted(): Boolean {
-        val channel = Channel<Boolean>()
-
-        // Register receiver
-        val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
-        val localBroadcastReceiver = object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                val action = intent.action ?: return
-                // We only want ACTION_STATUS messages
-                if (action != TorServiceConstants.ACTION_STATUS) {
-                    return
-                }
-                // The current status has the EXTRA_STATUS key
-                val currentStatus =
-                    intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
-                channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
-            }
-        }
-        lbm.registerReceiver(
-            localBroadcastReceiver,
-            IntentFilter(TorServiceConstants.ACTION_STATUS)
-        )
-
-        // Request service status
-        sendServiceAction(TorServiceConstants.ACTION_STATUS)
-
-        // Wait for response and unregister receiver
-        var torIsStarted = false
-        withTimeoutOrNull(torServiceResponseTimeout) {
-            torIsStarted = channel.receive()
-        }
-        lbm.unregisterReceiver(localBroadcastReceiver)
-        return torIsStarted
-    }
-
-    fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) {
-        if (BuildConfig.DISABLE_TOR) {
-            return
-        }
-
-        context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
-            .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
-
-        if (lifecycleScope == null) {
-            sendServiceAction(TorServiceConstants.ACTION_START)
-        } else {
-            lifecycleScope.launch {
-                val torNeedsStart = !checkTorIsStarted()
-                if (torNeedsStart) {
-                    sendServiceAction(TorServiceConstants.ACTION_START)
-                }
-            }
-        }
-    }
-
-    fun stopTor() {
-        if (BuildConfig.DISABLE_TOR) {
-            return
-        }
-
-        val torService = Intent(context, TorService::class.java)
-        context.stopService(torService)
-    }
-
-    fun setTorStopped() {
-        lastKnownStatus = TorStatus.OFF
-        onTorStopped()
-    }
-
-    fun restartTor() {
-        // tor-android-service doesn't dynamically update the torrc file,
-        // and it doesn't use SETCONF, so we completely restart the service.
-        // However, don't restart if we aren't started and we weren't
-        // previously started.
-        if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
-            return
-        }
+    fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false)
+    fun stopTor()
+    fun setTorStopped()
+    fun restartTor()
+}
 
-        if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
-            // If we aren't started, but we were previously bootstrapped,
-            // then we handle a "restart" request as a "start" restart
-            initiateTorBootstrap()
-        } else {
-            // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
-            // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
-            // service.
-            isTorRestarting = true
-            stopTor()
-        }
-    }
 
-    private fun sendServiceAction(action: String) {
-        val torServiceStatus = Intent(context, TorService::class.java)
-        torServiceStatus.action = action
-        context.startService(torServiceStatus)
-    }
 
-    companion object {
-        const val torServiceResponseTimeout = 5000L
-    }
-}


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
=====================================
@@ -0,0 +1,289 @@
+package org.mozilla.fenix.tor
+
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LifecycleCoroutineScope
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import org.mozilla.fenix.ext.components
+import org.mozilla.geckoview.TorIntegrationAndroid
+import org.mozilla.geckoview.TorIntegrationAndroid.BootstrapStateChangeListener
+import org.mozilla.geckoview.TorSettings
+import org.mozilla.geckoview.TorSettings.BridgeBuiltinType
+import org.mozilla.geckoview.TorSettings.BridgeSource
+
+// Enum matching TorConnectState from TorConnect.sys.mjs that we get from onBootstrapStateChange
+internal enum class TorConnectState(val state: String) {
+    Initial("Initial"),
+    Configuring("Configuring"),
+    AutoBootstrapping("AutoBootstrapping"),
+    Bootstrapping("Bootstrapping"),
+    Error("Error"),
+    Bootstrapped("Bootstrapped"),
+    Disabled("Disabled");
+
+    fun isStarting() = this == Bootstrapping || this == AutoBootstrapping
+    fun isError() = this == Error
+
+    fun isStarted() = this == Bootstrapped
+
+    fun isOff() = this == Initial || this == Configuring || this == Disabled || this == Error
+
+
+    // Convert to TorStatus that firefox-android uses based on tor-android-service
+    fun toTorStatus(): TorStatus {
+        return when (this) {
+            Initial -> TorStatus.OFF
+            Configuring -> TorStatus.OFF
+            AutoBootstrapping -> TorStatus.STARTING
+            Bootstrapping -> TorStatus.STARTING
+            Error -> TorStatus.UNKNOWN
+            Bootstrapped -> TorStatus.ON
+            Disabled -> TorStatus.OFF
+        }
+    }
+}
+
+class TorControllerGV(
+    private val context: Context,
+) : TorController, TorEvents, BootstrapStateChangeListener {
+
+    private val TAG = "TorControllerGV"
+
+    private var torListeners = mutableListOf<TorEvents>()
+
+    private var lastKnownStatus = TorConnectState.Initial
+    private var wasTorBootstrapped = false
+    private var isTorRestarting = false
+
+    private var isTorBootstrapped = false
+        get() = ((lastKnownStatus.isStarted()) && wasTorBootstrapped)
+
+    private val entries = mutableListOf<Pair<String?, String?>>()
+    override val logEntries get() = entries
+    override val isStarting get() = lastKnownStatus.isStarting()
+    override val isRestarting get() = isTorRestarting
+    override val isBootstrapped get() = isTorBootstrapped
+    override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+    private fun getTorIntegration(): TorIntegrationAndroid {
+        return (context.components.core.engine as GeckoEngine).getTorIntegrationController()
+    }
+
+    private fun getTorSettings(): TorSettings? {
+        return getTorIntegration().getSettings()
+    }
+
+    override var bridgesEnabled: Boolean
+        get() {
+            return getTorSettings()?.bridgesEnabled ?: false
+        }
+        set(value) {
+            getTorSettings()?.let {
+                it.bridgesEnabled = value
+                getTorIntegration().setSettings(it, true, true)
+            }
+        }
+
+
+    override var bridgeTransport: TorBridgeTransportConfig
+        get() {
+            return when (getTorSettings()?.bridgesSource) {
+                BridgeSource.BuiltIn -> {
+                    when (getTorSettings()?.bridgesBuiltinType) {
+                        BridgeBuiltinType.Obfs4 -> TorBridgeTransportConfig.BUILTIN_OBFS4
+                        BridgeBuiltinType.MeekAzure -> TorBridgeTransportConfig.BUILTIN_MEEK_AZURE
+                        BridgeBuiltinType.Snowflake -> TorBridgeTransportConfig.BUILTIN_SNOWFLAKE
+                        else -> TorBridgeTransportConfig.USER_PROVIDED
+                    }
+
+                }
+
+                BridgeSource.UserProvided -> TorBridgeTransportConfig.USER_PROVIDED
+                else -> TorBridgeTransportConfig.USER_PROVIDED
+            }
+        }
+        set(value) {
+            getTorSettings()?.let {
+                if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+                    it.bridgesSource = BridgeSource.BuiltIn
+                } else {
+                    val bbt: BridgeBuiltinType = when (value) {
+                        TorBridgeTransportConfig.BUILTIN_OBFS4 -> BridgeBuiltinType.Obfs4
+                        TorBridgeTransportConfig.BUILTIN_MEEK_AZURE -> BridgeBuiltinType.MeekAzure
+                        TorBridgeTransportConfig.BUILTIN_SNOWFLAKE -> BridgeBuiltinType.Snowflake
+                        else -> BridgeBuiltinType.Invalid
+                    }
+                    it.bridgesBuiltinType = bbt
+                }
+                getTorIntegration().setSettings(it, true, true)
+            }
+        }
+
+
+    override var userProvidedBridges: String?
+        get() {
+            return getTorSettings()?.bridgeBridgeStrings?.joinToString("\r\n")
+        }
+        set(value) {
+            getTorSettings()?.let {
+                it.bridgeBridgeStrings = value?.split("\r\n")?.toTypedArray() ?: arrayOf<String>()
+                getTorIntegration().setSettings(it, true, true)
+            }
+        }
+
+    override fun start() {
+        getTorIntegration().registerBootstrapStateChangeListener(this)
+    }
+
+    override fun stop() {
+        getTorIntegration().unregisterBootstrapStateChangeListener(this)
+    }
+
+    // TorEvents
+    override fun onTorConnecting() {
+        synchronized(torListeners) {
+            torListeners.forEach { it.onTorConnecting() }
+        }
+    }
+
+    // TorEvents
+    override fun onTorConnected() {
+        synchronized(torListeners) {
+            torListeners.forEach { it.onTorConnected() }
+        }
+    }
+
+    // TorEvents
+    override fun onTorStatusUpdate(entry: String?, status: String?) {
+        synchronized(torListeners) {
+            torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+        }
+    }
+
+    // TorEvents
+    override fun onTorStopped() {
+        synchronized(torListeners) {
+            torListeners.forEach { it.onTorStopped() }
+        }
+    }
+
+    override fun registerTorListener(l: TorEvents) {
+        synchronized(torListeners) {
+            if (torListeners.contains(l)) {
+                return
+            }
+            torListeners.add(l)
+        }
+    }
+
+    override fun unregisterTorListener(l: TorEvents) {
+        synchronized(torListeners) {
+            if (!torListeners.contains(l)) {
+                return
+            }
+            torListeners.remove(l)
+        }
+    }
+
+    override fun initiateTorBootstrap(
+        lifecycleScope: LifecycleCoroutineScope?,
+        withDebugLogging: Boolean,
+    ) {
+        getTorIntegration().beginBootstrap()
+    }
+
+    override fun stopTor() {
+        getTorIntegration().cancelBootstrap()
+    }
+
+    override fun setTorStopped() {
+        lastKnownStatus = TorConnectState.Disabled
+        onTorStopped()
+    }
+
+    override fun restartTor() {
+        if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
+            // If we aren't started, but we were previously bootstrapped,
+            // then we handle a "restart" request as a "start" restart
+            initiateTorBootstrap()
+        } else {
+            // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
+            // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
+            // service.
+            isTorRestarting = true
+            stopTor()
+        }
+    }
+
+    // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
+    // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
+    // and state for firefox-android (designed for tor-android-service)
+    //   fun onTorConnecting()
+    //   fun onTorConnected()
+    //   fun onTorStatusUpdate(entry: String?, status: String?)
+    //   fun onTorStopped()
+
+    // TorEventsBootstrapStateChangeListener
+    override fun onBootstrapStateChange(newStateVal: String?) {
+        Log.d(TAG, "onBootstrapStateChange($newStateVal)")
+        val newState: TorConnectState = TorConnectState.valueOf(newStateVal ?: "Error")
+
+        if (newState.isError() && wasTorBootstrapped) {
+            stopTor()
+        }
+
+        if (newState.isStarted()) {
+            wasTorBootstrapped = true
+            onTorConnected()
+        }
+
+        if (wasTorBootstrapped && newState == TorConnectState.Configuring) {
+            wasTorBootstrapped = false
+            if (isTorRestarting) {
+                initiateTorBootstrap()
+            } else {
+                onTorStopped()
+            }
+        }
+
+        if (lastKnownStatus.isOff() && newState.isStarting()) {
+            isTorRestarting = false
+        }
+
+        lastKnownStatus = newState
+
+    }
+
+    // TorEventsBootstrapStateChangeListener
+    override fun onBootstrapProgress(progress: Double, status: String?, hasWarnings: Boolean) {
+        Log.d(TAG, "onBootstrapProgress($progress, $status, $hasWarnings)")
+        if (progress == 100.0) {
+            lastKnownStatus = TorConnectState.Bootstrapped
+            wasTorBootstrapped = true
+            onTorConnected()
+        } else {
+            lastKnownStatus = TorConnectState.Bootstrapping
+            onTorConnecting()
+
+        }
+        entries.add(Pair(status, lastKnownStatus.toTorStatus().status))
+        onTorStatusUpdate(status, lastKnownStatus.toTorStatus().status)
+    }
+
+    // TorEventsBootstrapStateChangeListener
+    override fun onBootstrapComplete() {
+        lastKnownStatus = TorConnectState.Bootstrapped
+        this.onTorConnected()
+    }
+
+    // TorEventsBootstrapStateChangeListener
+    override fun onBootstrapError(message: String?, details: String?) {
+        lastKnownStatus = TorConnectState.Error
+    }
+
+    // TorEventsBootstrapStateChangeListener
+    override fun onSettingsRequested() {
+        // noop
+    }
+}


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
=====================================
@@ -0,0 +1,332 @@
+package org.mozilla.fenix.tor
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+import org.mozilla.fenix.BuildConfig
+import org.torproject.android.service.TorService
+import org.torproject.android.service.TorServiceConstants
+import org.torproject.android.service.util.Prefs
+
+ at SuppressWarnings("TooManyFunctions")
+class TorControllerTAS (private val context: Context): TorController {
+    private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+    private val entries = mutableListOf<Pair<String?, String?>>()
+    override val logEntries get() = entries
+
+    private var torListeners = mutableListOf<TorEvents>()
+
+    private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
+    private var lockTorListenersMutation = false
+
+    private var lastKnownStatus = TorStatus.OFF
+    private var wasTorBootstrapped = false
+    private var isTorRestarting = false
+
+    // This may be a lie
+    private var isTorBootstrapped = false
+        get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
+
+    val isDebugLoggingEnabled get() =
+        context
+            .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+            .getBoolean("pref_enable_logging", false)
+
+    override val isStarting get() = lastKnownStatus.isStarting()
+    override val isRestarting get() = isTorRestarting
+    override val isBootstrapped get() = isTorBootstrapped
+    override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+    override var bridgesEnabled: Boolean
+        get() = Prefs.bridgesEnabled()
+        set(value) { Prefs.putBridgesEnabled(value) }
+
+    override var bridgeTransport: TorBridgeTransportConfig
+        get() {
+            return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
+                Prefs.getBridgesList()
+            )
+        }
+        set(value) {
+            if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+                // Don't set the pref when the value is USER_PROVIDED because
+                // "user_provided" is not a valid bridge or transport type.
+                // This call should be followed by setting userProvidedBridges.
+                return
+            }
+            Prefs.setBridgesList(value.transportName)
+        }
+
+    override var userProvidedBridges: String?
+        get() {
+            val bridges = Prefs.getBridgesList()
+            val bridgeType =
+                TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
+            return when (bridgeType) {
+                TorBridgeTransportConfig.USER_PROVIDED -> bridges
+                else -> null
+            }
+        }
+        set(value) {
+            Prefs.setBridgesList(value)
+        }
+
+    override fun start() {
+        // Register receiver
+        lbm.registerReceiver(
+            persistentBroadcastReceiver,
+            IntentFilter(TorServiceConstants.ACTION_STATUS)
+        )
+        lbm.registerReceiver(
+            persistentBroadcastReceiver,
+            IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
+        )
+    }
+
+    override fun stop() {
+        lbm.unregisterReceiver(persistentBroadcastReceiver)
+    }
+
+    private val persistentBroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.action == null ||
+                (intent.action != TorServiceConstants.ACTION_STATUS &&
+                        intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
+            ) {
+                return
+            }
+            val action = intent.action
+
+            val logentry: String?
+            val status: String?
+            if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
+                logentry = intent.getExtras()
+                    ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
+            } else {
+                logentry = null
+            }
+
+            status = intent.getExtras()
+                ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
+
+            if (logentry == null && status == null) {
+                return
+            }
+
+            onTorStatusUpdate(logentry, status)
+
+            if (status == null) {
+                return
+            }
+
+            val newStatus = TorStatus.fromString(status)
+
+            if (newStatus.isUnknown() && wasTorBootstrapped) {
+                stopTor()
+            }
+
+            entries.add(Pair(logentry, status))
+
+            if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
+                wasTorBootstrapped = true
+                onTorConnected()
+            }
+
+            if (lastKnownStatus.isStopping() && newStatus.isOff()) {
+                if (isTorRestarting) {
+                    initiateTorBootstrap()
+                } else {
+                    onTorStopped()
+                }
+            }
+
+            if (lastKnownStatus.isOff() && newStatus.isStarting()) {
+                isTorRestarting = false
+            }
+
+            lastKnownStatus = newStatus
+        }
+    }
+
+    override fun onTorConnecting() {
+        lockTorListenersMutation = true
+        torListeners.forEach { it.onTorConnecting() }
+        lockTorListenersMutation = false
+
+        handlePendingRegistrationChanges()
+    }
+
+    override fun onTorConnected() {
+        lockTorListenersMutation = true
+        torListeners.forEach { it.onTorConnected() }
+        lockTorListenersMutation = false
+
+        handlePendingRegistrationChanges()
+    }
+
+    override fun onTorStatusUpdate(entry: String?, status: String?) {
+        lockTorListenersMutation = true
+        torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+        lockTorListenersMutation = false
+
+        handlePendingRegistrationChanges()
+    }
+
+    override fun onTorStopped() {
+        lockTorListenersMutation = true
+        torListeners.forEach { it.onTorStopped() }
+        lockTorListenersMutation = false
+
+        handlePendingRegistrationChanges()
+    }
+
+    override fun registerTorListener(l: TorEvents) {
+        if (torListeners.contains(l)) {
+            return
+        }
+
+        if (lockTorListenersMutation) {
+            pendingRegisterChangeList.add(Pair(l, true))
+        } else {
+            torListeners.add(l)
+        }
+    }
+
+    override fun unregisterTorListener(l: TorEvents) {
+        if (!torListeners.contains(l)) {
+            return
+        }
+
+        if (lockTorListenersMutation) {
+            pendingRegisterChangeList.add(Pair(l, false))
+        } else {
+            torListeners.remove(l)
+        }
+    }
+
+    private fun handlePendingRegistrationChanges() {
+        pendingRegisterChangeList.forEach {
+            if (it.second) {
+                registerTorListener(it.first)
+            } else {
+                unregisterTorListener(it.first)
+            }
+        }
+
+        pendingRegisterChangeList.clear()
+    }
+
+    /**
+     * Receive the current Tor status.
+     *
+     * Send a request for the current status and receive the response.
+     * Returns true if Tor is running, false otherwise.
+     *
+     */
+    private suspend fun checkTorIsStarted(): Boolean {
+        val channel = Channel<Boolean>()
+
+        // Register receiver
+        val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+        val localBroadcastReceiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                val action = intent.action ?: return
+                // We only want ACTION_STATUS messages
+                if (action != TorServiceConstants.ACTION_STATUS) {
+                    return
+                }
+                // The current status has the EXTRA_STATUS key
+                val currentStatus =
+                    intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
+                channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
+            }
+        }
+        lbm.registerReceiver(
+            localBroadcastReceiver,
+            IntentFilter(TorServiceConstants.ACTION_STATUS)
+        )
+
+        // Request service status
+        sendServiceAction(TorServiceConstants.ACTION_STATUS)
+
+        // Wait for response and unregister receiver
+        var torIsStarted = false
+        withTimeoutOrNull(torServiceResponseTimeout) {
+            torIsStarted = channel.receive()
+        }
+        lbm.unregisterReceiver(localBroadcastReceiver)
+        return torIsStarted
+    }
+
+    override fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope?, withDebugLogging: Boolean) {
+        if (BuildConfig.DISABLE_TOR) {
+            return
+        }
+
+        context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+            .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
+
+        if (lifecycleScope == null) {
+            sendServiceAction(TorServiceConstants.ACTION_START)
+        } else {
+            lifecycleScope.launch {
+                val torNeedsStart = !checkTorIsStarted()
+                if (torNeedsStart) {
+                    sendServiceAction(TorServiceConstants.ACTION_START)
+                }
+            }
+        }
+    }
+
+    override fun stopTor() {
+        if (BuildConfig.DISABLE_TOR) {
+            return
+        }
+
+        val torService = Intent(context, TorService::class.java)
+        context.stopService(torService)
+    }
+
+    override fun setTorStopped() {
+        lastKnownStatus = TorStatus.OFF
+        onTorStopped()
+    }
+
+    override fun restartTor() {
+        // tor-android-service doesn't dynamically update the torrc file,
+        // and it doesn't use SETCONF, so we completely restart the service.
+        // However, don't restart if we aren't started and we weren't
+        // previously started.
+        if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
+            return
+        }
+
+        if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
+            // If we aren't started, but we were previously bootstrapped,
+            // then we handle a "restart" request as a "start" restart
+            initiateTorBootstrap()
+        } else {
+            // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
+            // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
+            // service.
+            isTorRestarting = true
+            stopTor()
+        }
+    }
+
+    private fun sendServiceAction(action: String) {
+        val torServiceStatus = Intent(context, TorService::class.java)
+        torServiceStatus.action = action
+        context.startService(torServiceStatus)
+    }
+
+    companion object {
+        const val torServiceResponseTimeout = 5000L
+    }
+}



View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0a64536f0208b482feefe64fe0b7e43214a96c

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0a64536f0208b482feefe64fe0b7e43214a96c
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20240126/013055b6/attachment-0001.htm>


More information about the tbb-commits mailing list