Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
54b613b
Issue #1509: Reworked Kindness Mode initial page.
tladesignz Apr 22, 2026
6bfcb7e
Issue #1509: Reworked Kindness Mode main page. Added proxy quality ch…
tladesignz Apr 27, 2026
5ecc9b0
Issue #1509: Reworked Usage Limits bottom sheet.
tladesignz Apr 27, 2026
02581a7
Issue #1509: Added "testing" scene. (not functional, yet.)
tladesignz Apr 27, 2026
15be336
For now, or at least before I get intot tla's large kindness mode rev…
bitmold Apr 29, 2026
9c3f5c6
Merge branch 'staging' into kindness_mode
bitmold Apr 29, 2026
37a8b10
Merge branch 'master' into kindness_mode
bitmold Apr 29, 2026
f57b978
Issue #1509: Removed legacy test.
tladesignz Apr 30, 2026
c42ed21
Issue #1509: Fixed header inset. Use card with rounded corners simila…
tladesignz Apr 30, 2026
e7e5513
Issue #1509: Removed remnant of legacy test.
tladesignz Apr 30, 2026
2354c19
Issue #1509: Added first test mock, which at least implements 24 hour…
tladesignz Apr 30, 2026
5f98239
Issue #1509: Added first test mock, which at least implements 24 hour…
tladesignz Apr 30, 2026
60c793f
Issue #1509: Disabled binding to SnowflakeProxyService for now, since…
tladesignz Apr 30, 2026
c9272b7
Issue #1509: Fixed proxy quality UI initial state.
tladesignz Apr 30, 2026
dab4f52
Issue #1509: Added layout for landscape with different arrangement of…
tladesignz Apr 30, 2026
7f07059
aligned controls on usage limits bottom sheet
bitmold May 4, 2026
88f151b
Issue #1509: Improved "Usage Limits" sheet layout.
tladesignz May 6, 2026
a161ddb
Issue #1509: More improvements to "Usage Limits" sheet layout.
tladesignz May 6, 2026
196096c
Merge branch 'master' into kindness_mode
tladesignz May 6, 2026
ee89420
Merge branch 'master' into kindness_mode
tladesignz May 8, 2026
b1bcfe4
Issue #1509: Also don't forget to disable any proxy!
tladesignz May 8, 2026
b9fcdbb
Merge branch 'master' into kindness_mode
bitmold May 15, 2026
13c350f
get tor connected state from activity in testingdialogfragment. repla…
bitmold May 15, 2026
e22bff7
bump to gradle 9.5.1
bitmold May 15, 2026
c66a059
Translated using Weblate (Arabic)
May 15, 2026
3b4630f
removed stub unit test, only tests we use our androidTests for screen…
bitmold May 16, 2026
28a04d5
Translated using Weblate (Irish)
aindriu80 May 17, 2026
2a3ee72
Translated using Weblate (Japanese)
May 17, 2026
7c5d163
Fixed regression introduced in #1688 for when you dont have an outbou…
bitmold May 17, 2026
aad3169
snowflake proxy test logic to disconnect from tor if the user is curr…
bitmold May 18, 2026
dc3ac44
logic to connect to an instance of TorService in the snowflake proxy …
bitmold May 18, 2026
ea08dda
In the snowflake proxy testing flow, respawn OrbotService if we had t…
bitmold May 18, 2026
d805fe5
clean up debugging logic
bitmold May 18, 2026
b803222
implement timeout for tor connection test, cleanup and show failed UI…
bitmold May 18, 2026
f768de2
disable device rotation while the snowflake testing dialog is opened
bitmold May 18, 2026
f9e76b2
account for the user is totally offline edge case and immediately fai…
bitmold May 18, 2026
cfd40b7
Merge branch 'master' into kindness_mode
bitmold May 19, 2026
a363351
disable kindness mode when the user has explicitly set that theyre in…
bitmold May 20, 2026
e9bb460
put small start and end margins on the kindness mode header, on small…
bitmold May 20, 2026
a89b19c
assert that we need a quality check for running snowflake proxy when …
bitmold May 20, 2026
819c560
removed some compiler warnings from recent work, (unnecessary null ch…
bitmold May 21, 2026
20f6894
for now restrict taking the snowflake proxy test in Iran and Afghanis…
bitmold May 25, 2026
1fefdb7
change message for countries where snowflake test is disabled outright
bitmold May 25, 2026
d8477ae
Update Kindness Mode Quality Check:
bitmold Jun 1, 2026
22889d3
TODO add comment about bug surrounding the NoInternet state. Orbot th…
bitmold Jun 1, 2026
8f9a9d6
Kindness Mode Connection test first exhausts every way to determine i…
bitmold Jun 1, 2026
d7bc7c2
Merge branch 'master' into kindness_mode
bitmold Jun 2, 2026
f1fd93f
Use Regionalization.kt to disable kindness mode in extremely censored…
bitmold Jun 2, 2026
ca15b8c
display error messages when failing kindness mode evaluation. for ins…
bitmold Jun 2, 2026
b97b9d2
cleaned up dialog so far, used sensible names for view IDs, removed …
bitmold Jun 2, 2026
6e27d5b
Merge branch 'master' into kindness_mode
bitmold Jun 2, 2026
cfacd7f
alwyas stop snowflake proxy on new network events, and only restart i…
bitmold Jun 4, 2026
9859888
Merge branch 'master' into kindness_mode
bitmold Jun 4, 2026
411f0a9
use kotlin duration instead of long value for milleseconds
bitmold Jun 4, 2026
effbb98
NAT type is updated without binding to the service.
bitmold Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
android:required="false" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Expand Down Expand Up @@ -45,11 +46,11 @@
<application
android:name=".OrbotApp"
android:allowBackup="false"
android:fullBackupContent="false"
android:allowClearUserData="true"
android:configChanges="locale|orientation|screenSize"
android:dataExtractionRules="@xml/data_extraction_rules"
android:description="@string/app_description"
android:fullBackupContent="false"
android:hasFragileUserData="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand Down Expand Up @@ -120,6 +121,11 @@
android:foregroundServiceType="remoteMessaging"
android:stopWithTask="false" />

<service
android:name=".ui.kindness.TestTorForSnowflakeProxyService"
android:enabled="true"
android:stopWithTask="true" />


<service
android:name=".service.OrbotService"
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/org/torproject/android/Regionalization.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.torproject.android

import org.torproject.android.util.Prefs
import java.util.Locale

object Regionalization {

// converts a code like "ES" into "Spain", "Espagne", etc. based on the users current locale
@JvmStatic
fun getLocalizedNameForCountryCode(countryCode: String): String =
fun getLocalizedNameForCountryCode(countryCode: String? = Prefs.bridgeCountry): String =
Locale.Builder().setRegion(countryCode).build().displayCountry


Expand All @@ -19,6 +21,9 @@ object Regionalization {
return String(Character.toChars(firstChar)) + String(Character.toChars(secondChar))
}

fun isKindnessModeDisabledForCountry(countryCode: String? = Prefs.bridgeCountry): Boolean =
listOf(AFGHANISTAN, IRAN).contains(countryCode?.uppercase())

fun getCountriesForExitNodeUi(): List<String> =
listOf(
GERMANY, AUSTRIA, SWEDEN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,6 @@ object OrbotConstants {
"app.accrescent.client"
)

const val ONION_EMOJI: String = "\uD83E\uDDC5"


// Constants for getting bridges in semi-manual ways.

val GET_BRIDES_BRIDGES_URI = "https://bridges.torproject.org/".toUri()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,6 @@ public void onReceive(Context context, Intent intent) {
// hack for https://github.com/guardianproject/tor-android/issues/73 remove when fixed
var newStatus = intent.getStringExtra(EXTRA_STATUS);

if (STATUS_ON.equals(newStatus) && Prefs.getTransport() == Transport.NONE && !Prefs.getHasDirectConnected()) {
Prefs.setHasDirectConnected(true);
}

if (STATUS_OFF.equals(mCurrentStatus) && STATUS_STOPPING.equals(newStatus))
break;
mCurrentStatus = newStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ data class BuiltInBridges(
*/
fun getUdpDnstt(context: Context, countryCode: String?): List<Bridge>? {
if (countryCode.isNullOrEmpty()) return null
if (countryCode != "global" && !dnsCountries.contains(countryCode.lowercase())) return null
if (countryCode != "global" && !Regionalization.getCountriesWithDnsttSupport().contains(countryCode)) return null

val dnsInfo: DnsInfo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.graphics.Color
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.FrameLayout

Expand All @@ -21,7 +22,9 @@ import org.torproject.android.R
Class to set up default bottom sheet behavior for Config Connection, MOAT and any other
bottom sheets to come
*/
open class OrbotBottomSheetDialogFragment : BottomSheetDialogFragment() {
open class OrbotBottomSheetDialogFragment(
val minMode: Boolean = false
) : BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(requireActivity(), theme)
dialog.setOnShowListener {
Expand All @@ -30,25 +33,33 @@ open class OrbotBottomSheetDialogFragment : BottomSheetDialogFragment() {
bottomSheetView?.let {
it.setBackgroundResource(R.drawable.bottom_sheet_rounded)
it.setBackgroundColor(Color.TRANSPARENT)
setHeightResponsive(it)
val behavior = BottomSheetBehavior.from(it)
setHeightResponsive(it, behavior)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}

return dialog
}

private fun setHeightResponsive(bottomSheet: View) {
private fun setHeightResponsive(bottomSheet: View, behavior: BottomSheetBehavior<*>) {
val windowMetrics = WindowMetricsCalculator
.getOrCreate()
.computeCurrentWindowMetrics(requireActivity())

val windowHeight = windowMetrics.bounds.height()
val height = (windowHeight * getHeightRatio()).toInt()

val layoutParams = bottomSheet.layoutParams
layoutParams.height = height

if (minMode) {
behavior.maxHeight = height

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
else {
layoutParams.height = height
}

bottomSheet.layoutParams = layoutParams
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import org.torproject.android.Regionalization
import org.torproject.android.databinding.ConfigConnectionBottomSheetBinding
import org.torproject.android.service.OrbotConstants
import org.torproject.android.service.circumvention.AutoConf
import org.torproject.android.service.circumvention.BuiltInBridges
import org.torproject.android.service.circumvention.Transport
import org.torproject.android.util.Prefs
import org.torproject.android.ui.OrbotBottomSheetDialogFragment
Expand Down Expand Up @@ -90,7 +89,9 @@ class ConfigConnectionBottomSheet :
binding.acCountry.onItemClickListener = this

binding.dnsttContainer.visibility =
if (BuiltInBridges.dnsCountries.contains(selectedCountryCode?.lowercase())) View.VISIBLE else View.GONE
if (Regionalization.getCountriesWithDnsttSupport()
.contains(selectedCountryCode)
) View.VISIBLE else View.GONE

radios = arrayListOf(
binding.rbDirect,
Expand Down Expand Up @@ -428,7 +429,7 @@ class ConfigConnectionBottomSheet :
}

private fun updateDnsttVisibility() {
if (BuiltInBridges.dnsCountries.contains(selectedCountryCode?.lowercase())) {
if (Regionalization.getCountriesWithDnsttSupport().contains(selectedCountryCode)) {
binding.dnsttContainer.visibility = View.VISIBLE
} else {
binding.dnsttContainer.visibility = View.GONE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package org.torproject.android.ui.connect

sealed class ConnectUiState {
/**
* NoInternet can mean two things, the device doesn't have *any* reliable WiFi/Cellular/USB signal
* TODO If the device has a VALID WIFI CONNECTION, but the device is using another VPN that is
* blocking Orbot from connecting to the web, NoInternet DOES NOT REGISTER, even though Orbot
* is effectively offline...
*/
object NoInternet : ConnectUiState()
object Off : ConnectUiState()
data class Starting(val bootstrapPercent: Int?) : ConnectUiState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,45 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.appcompat.widget.SwitchCompat
import androidx.fragment.app.FragmentActivity
import org.torproject.android.R
import org.torproject.android.util.Prefs
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.setFragmentResult
import org.torproject.android.databinding.KindnessConfigBottomSheetBinding
import org.torproject.android.ui.OrbotBottomSheetDialogFragment
import org.torproject.android.util.Prefs

class KindnessConfigBottomSheet : OrbotBottomSheetDialogFragment() {
class KindnessConfigBottomSheet : OrbotBottomSheetDialogFragment(true) {

private lateinit var btnAction: Button
private lateinit var mBinding: KindnessConfigBottomSheetBinding

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.kindess_config_bottom_sheet, container, false)
v.findViewById<View>(R.id.tvCancel).setOnClickListener { dismiss() }
btnAction = v.findViewById(R.id.btnAction)
): View {
mBinding = KindnessConfigBottomSheetBinding.inflate(inflater, container, false)

mBinding.tvCancel.setOnClickListener { dismiss() }

val configWifi = v.findViewById<SwitchCompat>(R.id.swKindnessConfigWifi)
val configCharging = v.findViewById<SwitchCompat>(R.id.swKindnessConfigCharging)
mBinding.btnAction.setOnClickListener {
Prefs.setBeSnowflakeProxyLimitWifi(mBinding.swKindnessConfigWifi.isChecked)
Prefs.setBeSnowflakeProxyLimitCharging(mBinding.swKindnessConfigCharging.isChecked)

btnAction.setOnClickListener {
Prefs.setBeSnowflakeProxyLimitWifi(configWifi.isChecked)
Prefs.setBeSnowflakeProxyLimitCharging(configCharging.isChecked)
setFragmentResult(KEY_CONFIG_CHANGED, Bundle())
dismiss()
}

configWifi.isChecked = Prefs.limitSnowflakeProxyingWifi()
configCharging.isChecked = Prefs.limitSnowflakeProxyingCharging()
return v
mBinding.swKindnessConfigWifi.isChecked = Prefs.limitSnowflakeProxyingWifi()
mBinding.swKindnessConfigCharging.isChecked = Prefs.limitSnowflakeProxyingCharging()

return mBinding.root
}

companion object {
fun openKindnessSettings(fragmentActivity: FragmentActivity) {
const val KEY_CONFIG_CHANGED = "kindness_config_changed"

fun show(fragmentManager: FragmentManager) {
KindnessConfigBottomSheet().show(
fragmentActivity.supportFragmentManager,
fragmentManager,
"KindnessConfig"
)
}
}

}
}
Loading