Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
- 
2095a229
by clairehurst at 2024-04-17T00:27:30+00:00
4 changed files:
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsComposeFragment.kt
- − fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsFragment.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt
- fenix/app/src/main/res/navigation/nav_graph.xml
Changes:
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
 | |
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 4 | + | |
| 5 | +package org.mozilla.fenix.tor
 | |
| 6 | + | |
| 7 | +import android.os.Bundle
 | |
| 8 | +import android.view.LayoutInflater
 | |
| 9 | +import android.view.View
 | |
| 10 | +import android.view.ViewGroup
 | |
| 11 | +import androidx.compose.foundation.layout.Column
 | |
| 12 | +import androidx.compose.foundation.layout.fillMaxSize
 | |
| 13 | +import androidx.compose.foundation.layout.fillMaxWidth
 | |
| 14 | +import androidx.compose.foundation.layout.padding
 | |
| 15 | +import androidx.compose.foundation.rememberScrollState
 | |
| 16 | +import androidx.compose.foundation.text.selection.DisableSelection
 | |
| 17 | +import androidx.compose.foundation.text.selection.SelectionContainer
 | |
| 18 | +import androidx.compose.foundation.verticalScroll
 | |
| 19 | +import androidx.compose.material.Text
 | |
| 20 | +import androidx.compose.runtime.Composable
 | |
| 21 | +import androidx.compose.runtime.Stable
 | |
| 22 | +import androidx.compose.ui.Modifier
 | |
| 23 | +import androidx.compose.ui.platform.ComposeView
 | |
| 24 | +import androidx.compose.ui.unit.dp
 | |
| 25 | +import androidx.fragment.app.Fragment
 | |
| 26 | +import androidx.fragment.app.viewModels
 | |
| 27 | +import mozilla.components.ui.colors.PhotonColors
 | |
| 28 | + | |
| 29 | +class TorLogsComposeFragment : Fragment() {
 | |
| 30 | +    private val viewModel: TorLogsViewModel by viewModels()
 | |
| 31 | + | |
| 32 | +    override fun onCreateView(
 | |
| 33 | +        inflater: LayoutInflater,
 | |
| 34 | +        container: ViewGroup?,
 | |
| 35 | +        savedInstanceState: Bundle?,
 | |
| 36 | +    ): View {
 | |
| 37 | +        return ComposeView(requireContext()).apply {
 | |
| 38 | +            setContent {
 | |
| 39 | +                SelectionContainer {
 | |
| 40 | +                    Column(
 | |
| 41 | +                        // Column instead of LazyColumn so that you can select all the logs, and not just one "screen" at a time
 | |
| 42 | +                        // The logs won't be too big so loading them all instead of just whats visible shouldn't be a big deal
 | |
| 43 | +                        modifier = Modifier
 | |
| 44 | +                            .fillMaxSize()
 | |
| 45 | +                            .verticalScroll(state = rememberScrollState(), reverseScrolling = true),
 | |
| 46 | +                    ) {
 | |
| 47 | +                        for (log in viewModel.torLogs) {
 | |
| 48 | +                            LogRow(log = log)
 | |
| 49 | +                        }
 | |
| 50 | +                    }
 | |
| 51 | +                }
 | |
| 52 | +            }
 | |
| 53 | +        }
 | |
| 54 | +    }
 | |
| 55 | +}
 | |
| 56 | + | |
| 57 | +@Composable
 | |
| 58 | +@Stable
 | |
| 59 | +fun LogRow(log: TorLog, modifier: Modifier = Modifier) {
 | |
| 60 | +    Column(
 | |
| 61 | +        modifier
 | |
| 62 | +            .fillMaxWidth()
 | |
| 63 | +            .padding(
 | |
| 64 | +                start = 16.dp,
 | |
| 65 | +                end = 16.dp,
 | |
| 66 | +                bottom = 16.dp,
 | |
| 67 | +            ),
 | |
| 68 | +    ) {
 | |
| 69 | +        DisableSelection {
 | |
| 70 | +            Text(
 | |
| 71 | +                text = log.timestamp.toString(),
 | |
| 72 | +                color = PhotonColors.LightGrey40,
 | |
| 73 | +                modifier = modifier
 | |
| 74 | +                    .padding(bottom = 4.dp),
 | |
| 75 | +            )
 | |
| 76 | +        }
 | |
| 77 | +        Text(
 | |
| 78 | +            text = log.text,
 | |
| 79 | +            color = PhotonColors.LightGrey05,
 | |
| 80 | +        )
 | |
| 81 | +    }
 | |
| 82 | +} | 
| 1 | -/* This Source Code Form is subject to the terms of the Mozilla Public
 | |
| 2 | - * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| 3 | - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 4 | - | |
| 5 | -package org.mozilla.fenix.tor
 | |
| 6 | - | |
| 7 | -import android.os.Bundle
 | |
| 8 | -import android.text.method.ScrollingMovementMethod
 | |
| 9 | -import android.view.LayoutInflater
 | |
| 10 | -import android.view.View
 | |
| 11 | -import android.view.ViewGroup
 | |
| 12 | -import androidx.fragment.app.Fragment
 | |
| 13 | -import org.mozilla.fenix.R
 | |
| 14 | -import org.mozilla.fenix.components.Components
 | |
| 15 | -import org.mozilla.fenix.databinding.TorBootstrapLoggerBinding
 | |
| 16 | -import org.mozilla.fenix.ext.requireComponents
 | |
| 17 | -import org.mozilla.fenix.tor.view.TorBootstrapLoggerViewHolder
 | |
| 18 | - | |
| 19 | -class TorLogsFragment : Fragment(), TorLogs {
 | |
| 20 | - | |
| 21 | -    private var entries = mutableListOf<String>()
 | |
| 22 | -    internal var _binding: TorBootstrapLoggerBinding? = null
 | |
| 23 | -    private val binding get() = _binding!!
 | |
| 24 | - | |
| 25 | -    private var _components: Components? = null
 | |
| 26 | -    private val components get() = _components!!
 | |
| 27 | - | |
| 28 | -    override fun onCreateView(
 | |
| 29 | -        inflater: LayoutInflater,
 | |
| 30 | -        container: ViewGroup?,
 | |
| 31 | -        savedInstanceState: Bundle?,
 | |
| 32 | -    ): View {
 | |
| 33 | -        _binding = TorBootstrapLoggerBinding.inflate(inflater)
 | |
| 34 | -        _components = requireComponents
 | |
| 35 | - | |
| 36 | -        components.torController.registerTorLogListener(this)
 | |
| 37 | - | |
| 38 | -        val currentEntries = components.torController.logEntries.filter { it.second != null }
 | |
| 39 | -            .filter { !(it.second!!.startsWith("Circuit") && it.first == "ON") }
 | |
| 40 | -            // Keep synchronized with format in onTorStatusUpdate
 | |
| 41 | -            .flatMap { listOf("(${it.first}) '${it.second}'") }
 | |
| 42 | -        val entriesLen = currentEntries.size
 | |
| 43 | -        val subListOffset =
 | |
| 44 | -            if (entriesLen > TorBootstrapLoggerViewHolder.MAX_NEW_ENTRIES) TorBootstrapLoggerViewHolder.MAX_NEW_ENTRIES else entriesLen
 | |
| 45 | -        entries =
 | |
| 46 | -            currentEntries.subList((entriesLen - subListOffset), entriesLen) as MutableList<String>
 | |
| 47 | -        val initLog =
 | |
| 48 | -            "---------------" + getString(R.string.tor_initializing_log) + "---------------"
 | |
| 49 | -        entries.add(0, initLog)
 | |
| 50 | - | |
| 51 | -        with(binding.torBootstrapLogEntries) {
 | |
| 52 | -            movementMethod = ScrollingMovementMethod()
 | |
| 53 | -            text = formatLogEntries(entries)
 | |
| 54 | -        }
 | |
| 55 | - | |
| 56 | - | |
| 57 | -        return binding.root
 | |
| 58 | -    }
 | |
| 59 | - | |
| 60 | -    // TODO on destroy unregiuster
 | |
| 61 | - | |
| 62 | -    private fun formatLogEntries(entries: List<String>) = entries.joinToString("\n")
 | |
| 63 | - | |
| 64 | -    override fun onLog(type: String?, message: String?) {
 | |
| 65 | -        if (message == null || type == null) return
 | |
| 66 | -        if (type == "ON" && type.startsWith("Circuit")) return
 | |
| 67 | - | |
| 68 | -        if (entries.size > TorBootstrapLoggerViewHolder.MAX_LINES) {
 | |
| 69 | -            entries = entries.drop(1) as MutableList<String>
 | |
| 70 | -        }
 | |
| 71 | -        entries.add("($type) '$message'")
 | |
| 72 | - | |
| 73 | -        binding.torBootstrapLogEntries.text = formatLogEntries(entries)
 | |
| 74 | -    }
 | |
| 75 | - | |
| 76 | -    override fun onStop() {
 | |
| 77 | -        super.onStop()
 | |
| 78 | -        components.torController.unregisterTorLogListener(this)
 | |
| 79 | -    }
 | |
| 80 | - | |
| 81 | -} | 
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
 | |
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 4 | + | |
| 5 | +package org.mozilla.fenix.tor
 | |
| 6 | + | |
| 7 | +import android.app.Application
 | |
| 8 | +import android.content.ClipData
 | |
| 9 | +import android.content.ClipboardManager
 | |
| 10 | +import android.content.Context
 | |
| 11 | +import android.os.Build
 | |
| 12 | +import android.widget.Toast
 | |
| 13 | +import androidx.compose.runtime.Stable
 | |
| 14 | +import androidx.lifecycle.AndroidViewModel
 | |
| 15 | +import org.mozilla.fenix.R
 | |
| 16 | +import org.mozilla.fenix.ext.components
 | |
| 17 | +import java.sql.Timestamp
 | |
| 18 | + | |
| 19 | +class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogs {
 | |
| 20 | +    private val torController = application.components.torController
 | |
| 21 | +    private val clipboardManager =
 | |
| 22 | +        application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
 | |
| 23 | + | |
| 24 | +    val torLogs: MutableList<TorLog> = mutableListOf(
 | |
| 25 | +        TorLog(
 | |
| 26 | +            "---------------" + application.getString(R.string.tor_initializing_log) + "---------------",
 | |
| 27 | +        ),
 | |
| 28 | +    )
 | |
| 29 | + | |
| 30 | +    init {
 | |
| 31 | +        setupClipboardListener()
 | |
| 32 | +        torController.registerTorLogListener(this)
 | |
| 33 | +        val currentEntries = torController.logEntries.filter { it.second != null }
 | |
| 34 | +            .filter { !(it.second!!.startsWith("Circuit") && it.first == "ON") }
 | |
| 35 | +            // Keep synchronized with format in onTorStatusUpdate
 | |
| 36 | +            .flatMap { listOf(TorLog("[${it.first}] ${it.second}")) }
 | |
| 37 | +        torLogs.addAll(currentEntries)
 | |
| 38 | +    }
 | |
| 39 | + | |
| 40 | +    override fun onLog(type: String?, message: String?) {
 | |
| 41 | +        if (message == null || type == null) return
 | |
| 42 | +        if (type == "ON" && type.startsWith("Circuit")) return
 | |
| 43 | + | |
| 44 | +        torLogs.add(TorLog("[$type] $message"))
 | |
| 45 | +    }
 | |
| 46 | + | |
| 47 | +    override fun onCleared() {
 | |
| 48 | +        super.onCleared()
 | |
| 49 | +        torController.unregisterTorLogListener(this)
 | |
| 50 | +    }
 | |
| 51 | + | |
| 52 | +    private fun setupClipboardListener() {
 | |
| 53 | +        clipboardManager.addPrimaryClipChangedListener {
 | |
| 54 | +            // Only show a toast for Android 12 and lower.
 | |
| 55 | +            // https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
 | |
| 56 | +            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
 | |
| 57 | +                Toast.makeText(
 | |
| 58 | +                    getApplication<Application>().applicationContext,
 | |
| 59 | +                    getApplication<Application>().getString(R.string.toast_copy_link_to_clipboard), // "Copied to clipboard" already translated
 | |
| 60 | +                    Toast.LENGTH_SHORT,
 | |
| 61 | +                ).show()
 | |
| 62 | +            }
 | |
| 63 | +        }
 | |
| 64 | +    }
 | |
| 65 | + | |
| 66 | +    fun copyAllLogsToClipboard() { // TODO add kebab menu in top right corner which includes option to "Copy all logs"
 | |
| 67 | +        clipboardManager.setPrimaryClip(
 | |
| 68 | +            ClipData.newPlainText(
 | |
| 69 | +                "Copied Text",
 | |
| 70 | +                getAllTorLogs(),
 | |
| 71 | +            ),
 | |
| 72 | +        )
 | |
| 73 | +    }
 | |
| 74 | + | |
| 75 | +    private fun getAllTorLogs(): String {
 | |
| 76 | +        var ret = ""
 | |
| 77 | +        for (log in torLogs) {
 | |
| 78 | +            ret += log.text + '\n'
 | |
| 79 | +        }
 | |
| 80 | +        return ret
 | |
| 81 | +    }
 | |
| 82 | +}
 | |
| 83 | + | |
| 84 | +@Stable
 | |
| 85 | +data class TorLog(
 | |
| 86 | +    val text: String,
 | |
| 87 | +    val timestamp: Timestamp = Timestamp(System.currentTimeMillis()),
 | |
| 88 | +) | 
| ... | ... | @@ -976,8 +976,7 @@ | 
| 976 | 976 |      <fragment
 | 
| 977 | 977 |          android:id="@+id/torBridgeConfigFragment"
 | 
| 978 | 978 |          android:name="org.mozilla.fenix.settings.TorBridgeConfigFragment"
 | 
| 979 | -        android:label="@string/preferences_tor_network_settings_bridge_config"
 | |
| 980 | -        tools:layout="@layout/fragment_tor_bridge_config" />
 | |
| 979 | +        android:label="@string/preferences_tor_network_settings_bridge_config" />
 | |
| 981 | 980 |      <fragment
 | 
| 982 | 981 |          android:id="@+id/torBetaConnectionFeaturesFragment"
 | 
| 983 | 982 |          android:name="org.mozilla.fenix.tor.TorBetaConnectionFeaturesFragment"
 | 
| ... | ... | @@ -985,9 +984,8 @@ | 
| 985 | 984 |          tools:layout="@layout/tor_network_settings_beta_connection_features" />
 | 
| 986 | 985 |      <fragment
 | 
| 987 | 986 |          android:id="@+id/torLogsFragment"
 | 
| 988 | -        android:name="org.mozilla.fenix.tor.TorLogsFragment"
 | |
| 989 | -        android:label="Tor Logs"
 | |
| 990 | -        tools:layout="@layout/tor_bootstrap_logger" />
 | |
| 987 | +        android:name="org.mozilla.fenix.tor.TorLogsComposeFragment"
 | |
| 988 | +        android:label="@string/preferences_tor_logs" />
 | |
| 991 | 989 | |
| 992 | 990 |      <fragment
 | 
| 993 | 991 |          android:id="@+id/trackingProtectionFragment"
 |