ui: reformat all code

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2023-05-05 03:09:38 +02:00
parent a3bfa6f1ab
commit 40eaa54cf0
47 changed files with 360 additions and 281 deletions

View File

@ -468,6 +468,7 @@
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="160" />
</codeStyleSettings> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

View File

@ -1,4 +1,5 @@
@file:Suppress("UnstableApiUsage") @file:Suppress("UnstableApiUsage")
import org.gradle.api.tasks.testing.logging.TestLogEvent import org.gradle.api.tasks.testing.logging.TestLogEvent
val pkg: String = providers.gradleProperty("wireguardPackageName").get() val pkg: String = providers.gradleProperty("wireguardPackageName").get()

View File

@ -1,4 +1,5 @@
@file:Suppress("UnstableApiUsage") @file:Suppress("UnstableApiUsage")
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

View File

@ -1,8 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:node="remove" />
<application> <application>
<receiver android:name=".updater.Updater$AppUpdatedReceiver" tools:node="remove" /> <receiver
android:name=".updater.Updater$AppUpdatedReceiver"
tools:node="remove" />
</application> </application>
</manifest> </manifest>

View File

@ -45,10 +45,12 @@
<activity <activity
android:name=".activity.TunnelToggleActivity" android:name=".activity.TunnelToggleActivity"
android:theme="@style/NoBackgroundTheme" android:excludeFromRecents="true"
android:excludeFromRecents="true"/> android:theme="@style/NoBackgroundTheme" />
<activity android:name=".activity.MainActivity" android:exported="true"> <activity
android:name=".activity.MainActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -62,8 +64,8 @@
<activity <activity
android:name=".activity.TvMainActivity" android:name=".activity.TvMainActivity"
android:theme="@style/TvTheme" android:exported="true"
android:exported="true"> android:theme="@style/TvTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
@ -87,8 +89,8 @@
<activity <activity
android:name=".activity.LogViewerActivity" android:name=".activity.LogViewerActivity"
android:label="@string/log_viewer_title" android:exported="false"
android:exported="false"> android:label="@string/log_viewer_title">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
@ -100,14 +102,18 @@
android:exported="false" android:exported="false"
android:grantUriPermissions="true" /> android:grantUriPermissions="true" />
<receiver android:name=".BootShutdownReceiver" android:exported="true"> <receiver
android:name=".BootShutdownReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" /> <action android:name="android.intent.action.ACTION_SHUTDOWN" />
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".updater.Updater$AppUpdatedReceiver" android:exported="true"> <receiver
android:name=".updater.Updater$AppUpdatedReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter> </intent-filter>
@ -115,8 +121,8 @@
<receiver <receiver
android:name=".model.TunnelManager$IntentReceiver" android:name=".model.TunnelManager$IntentReceiver"
android:permission="${applicationId}.permission.CONTROL_TUNNELS" android:exported="true"
android:exported="true"> android:permission="${applicationId}.permission.CONTROL_TUNNELS">
<intent-filter> <intent-filter>
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" /> <action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" /> <action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
@ -126,9 +132,9 @@
<service <service
android:name=".QuickTileService" android:name=".QuickTileService"
android:exported="true"
android:icon="@drawable/ic_tile" android:icon="@drawable/ic_tile"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />

View File

@ -67,7 +67,8 @@ abstract class BaseActivity : AppCompatActivity() {
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean
fun removeOnSelectedTunnelChangedListener( fun removeOnSelectedTunnelChangedListener(
listener: OnSelectedTunnelChangedListener) { listener: OnSelectedTunnelChangedListener
) {
selectionChangeRegistry.remove(listener) selectionChangeRegistry.remove(listener)
} }
@ -77,17 +78,17 @@ abstract class BaseActivity : AppCompatActivity() {
private class SelectionChangeNotifier : NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>() { private class SelectionChangeNotifier : NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>() {
override fun onNotifyCallback( override fun onNotifyCallback(
listener: OnSelectedTunnelChangedListener, listener: OnSelectedTunnelChangedListener,
oldTunnel: ObservableTunnel?, oldTunnel: ObservableTunnel?,
ignored: Int, ignored: Int,
newTunnel: ObservableTunnel? newTunnel: ObservableTunnel?
) { ) {
listener.onSelectedTunnelChanged(oldTunnel, newTunnel) listener.onSelectedTunnelChanged(oldTunnel, newTunnel)
} }
} }
private class SelectionChangeRegistry : private class SelectionChangeRegistry :
CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>(SelectionChangeNotifier()) CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>(SelectionChangeNotifier())
companion object { companion object {
private const val KEY_SELECTED_TUNNEL = "selected_tunnel" private const val KEY_SELECTED_TUNNEL = "selected_tunnel"

View File

@ -142,18 +142,20 @@ class LogViewerActivity : AppCompatActivity() {
finish() finish()
true true
} }
R.id.save_log -> { R.id.save_log -> {
saveButton?.isEnabled = false saveButton?.isEnabled = false
lifecycleScope.launch { saveLog() } lifecycleScope.launch { saveLog() }
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
private val downloadsFileSaver = DownloadsFileSaver(this) private val downloadsFileSaver = DownloadsFileSaver(this)
private suspend fun rawLogBytes() : ByteArray { private suspend fun rawLogBytes(): ByteArray {
val builder = StringBuilder() val builder = StringBuilder()
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (i in 0 until rawLogLines.size()) { for (i in 0 until rawLogLines.size()) {
@ -179,12 +181,14 @@ class LogViewerActivity : AppCompatActivity() {
saveButton?.isEnabled = true saveButton?.isEnabled = true
if (outputFile == null) if (outputFile == null)
return return
Snackbar.make(findViewById(android.R.id.content), Snackbar.make(
if (exception == null) getString(R.string.log_export_success, outputFile?.fileName) findViewById(android.R.id.content),
else getString(R.string.log_export_error, ErrorMessages[exception]), if (exception == null) getString(R.string.log_export_success, outputFile?.fileName)
if (exception == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG) else getString(R.string.log_export_error, ErrorMessages[exception]),
.setAnchorView(binding.shareFab) if (exception == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
.show() )
.setAnchorView(binding.shareFab)
.show()
} }
private suspend fun streamingLog() = withContext(Dispatchers.IO) { private suspend fun streamingLog() = withContext(Dispatchers.IO) {
@ -287,7 +291,8 @@ class LogViewerActivity : AppCompatActivity() {
* *
* <pre>05-26 11:02:36.886 5689 5689 D AndroidRuntime: CheckJNI is OFF.</pre> * <pre>05-26 11:02:36.886 5689 5689 D AndroidRuntime: CheckJNI is OFF.</pre>
*/ */
private val THREADTIME_LINE: Pattern = Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$") private val THREADTIME_LINE: Pattern =
Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$")
private val LOGS: MutableMap<String, ByteArray> = ConcurrentHashMap() private val LOGS: MutableMap<String, ByteArray> = ConcurrentHashMap()
private const val TAG = "WireGuard/LogViewerActivity" private const val TAG = "WireGuard/LogViewerActivity"
} }
@ -310,7 +315,7 @@ class LogViewerActivity : AppCompatActivity() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)
.inflate(R.layout.log_viewer_entry, parent, false) .inflate(R.layout.log_viewer_entry, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
@ -321,8 +326,10 @@ class LogViewerActivity : AppCompatActivity() {
else else
SpannableString("${line.tag}: ${line.msg}").apply { SpannableString("${line.tag}: ${line.msg}").apply {
setSpan(StyleSpan(BOLD), 0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(StyleSpan(BOLD), 0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setSpan(ForegroundColorSpan(levelToColor(line.level)), setSpan(
0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) ForegroundColorSpan(levelToColor(line.level)),
0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
holder.layout.apply { holder.layout.apply {
findViewById<MaterialTextView>(R.id.log_date).text = line.time.toString() findViewById<MaterialTextView>(R.id.log_date).text = line.time.toString()
@ -344,11 +351,11 @@ class LogViewerActivity : AppCompatActivity() {
override fun insert(uri: Uri, values: ContentValues?): Uri? = null override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? =
logForUri(uri)?.let { logForUri(uri)?.let {
val m = MatrixCursor(arrayOf(android.provider.OpenableColumns.DISPLAY_NAME, android.provider.OpenableColumns.SIZE), 1) val m = MatrixCursor(arrayOf(android.provider.OpenableColumns.DISPLAY_NAME, android.provider.OpenableColumns.SIZE), 1)
m.addRow(arrayOf("wireguard-log.txt", it.size.toLong())) m.addRow(arrayOf("wireguard-log.txt", it.size.toLong()))
m m
} }
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true
@ -358,7 +365,8 @@ class LogViewerActivity : AppCompatActivity() {
override fun getType(uri: Uri): String? = logForUri(uri)?.let { "text/plain" } override fun getType(uri: Uri): String? = logForUri(uri)?.let { "text/plain" }
override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? = getType(uri)?.let { if (compareMimeTypes(it, mimeTypeFilter)) arrayOf(it) else null } override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? =
getType(uri)?.let { if (compareMimeTypes(it, mimeTypeFilter)) arrayOf(it) else null }
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
if (mode != "r") return null if (mode != "r") return null

View File

@ -77,6 +77,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
true true
} }
R.id.menu_action_edit -> { R.id.menu_action_edit -> {
supportFragmentManager.commit { supportFragmentManager.commit {
replace(R.id.detail_container, TunnelEditorFragment()) replace(R.id.detail_container, TunnelEditorFragment())
@ -91,12 +92,15 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
startActivity(Intent(this, SettingsActivity::class.java)) startActivity(Intent(this, SettingsActivity::class.java))
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, override fun onSelectedTunnelChanged(
newTunnel: ObservableTunnel?): Boolean { oldTunnel: ObservableTunnel?,
newTunnel: ObservableTunnel?
): Boolean {
val fragmentManager = supportFragmentManager val fragmentManager = supportFragmentManager
if (fragmentManager.isStateSaved) { if (fragmentManager.isStateSaved) {
return false return false

View File

@ -62,9 +62,9 @@ class SettingsActivity : AppCompatActivity() {
zipExporter?.parent?.removePreference(zipExporter) zipExporter?.parent?.removePreference(zipExporter)
} }
val wgQuickOnlyPrefs = arrayOf( val wgQuickOnlyPrefs = arrayOf(
preferenceManager.findPreference("tools_installer"), preferenceManager.findPreference("tools_installer"),
preferenceManager.findPreference("restore_on_boot"), preferenceManager.findPreference("restore_on_boot"),
preferenceManager.findPreference<Preference>("multiple_tunnels") preferenceManager.findPreference<Preference>("multiple_tunnels")
).filterNotNull() ).filterNotNull()
wgQuickOnlyPrefs.forEach { it.isVisible = false } wgQuickOnlyPrefs.forEach { it.isVisible = false }
lifecycleScope.launch { lifecycleScope.launch {

View File

@ -24,7 +24,8 @@ import kotlinx.coroutines.launch
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
class TunnelToggleActivity : AppCompatActivity() { class TunnelToggleActivity : AppCompatActivity() {
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() } private val permissionActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() }
private fun toggleTunnelWithPermissionsResult() { private fun toggleTunnelWithPermissionsResult() {
val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return

View File

@ -211,12 +211,13 @@ class TvMainActivity : AppCompatActivity() {
try { try {
tunnelFileImportResultLauncher.launch("*/*") tunnelFileImportResultLauncher.launch("*/*")
} catch (_: Throwable) { } catch (_: Throwable) {
MaterialAlertDialogBuilder(binding.root.context).setMessage(R.string.tv_no_file_picker).setCancelable(false).setPositiveButton(android.R.string.ok) { _, _ -> MaterialAlertDialogBuilder(binding.root.context).setMessage(R.string.tv_no_file_picker).setCancelable(false)
try { .setPositiveButton(android.R.string.ok) { _, _ ->
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect"))) try {
} catch (_: Throwable) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
} } catch (_: Throwable) {
}.show() }
}.show()
} }
} }
} }
@ -359,6 +360,7 @@ class TvMainActivity : AppCompatActivity() {
binding.tunnelList.requestFocus() binding.tunnelList.requestFocus()
} }
} }
filesRoot.get()?.isNotEmpty() == true -> { filesRoot.get()?.isNotEmpty() == true -> {
files.clear() files.clear()
filesRoot.set("") filesRoot.set("")
@ -372,7 +374,7 @@ class TvMainActivity : AppCompatActivity() {
private suspend fun updateStats() { private suspend fun updateStats() {
binding.tunnelList.forEach { viewItem -> binding.tunnelList.forEach { viewItem ->
val listItem = DataBindingUtil.findBinding<TvTunnelListItemBinding>(viewItem) val listItem = DataBindingUtil.findBinding<TvTunnelListItemBinding>(viewItem)
?: return@forEach ?: return@forEach
try { try {
val tunnel = listItem.item!! val tunnel = listItem.item!!
if (tunnel.state != Tunnel.State.UP || isDeleting.get()) { if (tunnel.state != Tunnel.State.UP || isDeleting.get()) {

View File

@ -40,9 +40,9 @@ class FileConfigStore(private val context: Context) : ConfigStore {
override fun enumerate(): Set<String> { override fun enumerate(): Set<String> {
return context.fileList() return context.fileList()
.filter { it.endsWith(".conf") } .filter { it.endsWith(".conf") }
.map { it.substring(0, it.length - ".conf".length) } .map { it.substring(0, it.length - ".conf".length) }
.toSet() .toSet()
} }
private fun fileFor(name: String): File { private fun fileFor(name: String): File {

View File

@ -47,9 +47,11 @@ object BindingAdapters {
@JvmStatic @JvmStatic
@BindingAdapter("items", "layout", "fragment") @BindingAdapter("items", "layout", "fragment")
fun <E> setItems(view: LinearLayout, fun <E> setItems(
oldList: ObservableList<E>?, oldLayoutId: Int, @Suppress("UNUSED_PARAMETER") oldFragment: Fragment?, view: LinearLayout,
newList: ObservableList<E>?, newLayoutId: Int, newFragment: Fragment?) { oldList: ObservableList<E>?, oldLayoutId: Int, @Suppress("UNUSED_PARAMETER") oldFragment: Fragment?,
newList: ObservableList<E>?, newLayoutId: Int, newFragment: Fragment?
) {
if (oldList === newList && oldLayoutId == newLayoutId) if (oldList === newList && oldLayoutId == newLayoutId)
return return
var listener: ItemChangeListener<E>? = ListenerUtil.getListener(view, R.id.item_change_listener) var listener: ItemChangeListener<E>? = ListenerUtil.getListener(view, R.id.item_change_listener)
@ -73,9 +75,11 @@ object BindingAdapters {
@JvmStatic @JvmStatic
@BindingAdapter("items", "layout") @BindingAdapter("items", "layout")
fun <E> setItems(view: LinearLayout, fun <E> setItems(
oldList: Iterable<E>?, oldLayoutId: Int, view: LinearLayout,
newList: Iterable<E>?, newLayoutId: Int) { oldList: Iterable<E>?, oldLayoutId: Int,
newList: Iterable<E>?, newLayoutId: Int
) {
if (oldList === newList && oldLayoutId == newLayoutId) if (oldList === newList && oldLayoutId == newLayoutId)
return return
view.removeAllViews() view.removeAllViews()
@ -93,11 +97,13 @@ object BindingAdapters {
@JvmStatic @JvmStatic
@BindingAdapter(requireAll = false, value = ["items", "layout", "configurationHandler"]) @BindingAdapter(requireAll = false, value = ["items", "layout", "configurationHandler"])
fun <K, E : Keyed<out K>> setItems(view: RecyclerView, fun <K, E : Keyed<out K>> setItems(
oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int, view: RecyclerView,
@Suppress("UNUSED_PARAMETER") oldRowConfigurationHandler: RowConfigurationHandler<*, *>?, oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int,
newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int, @Suppress("UNUSED_PARAMETER") oldRowConfigurationHandler: RowConfigurationHandler<*, *>?,
newRowConfigurationHandler: RowConfigurationHandler<*, *>?) { newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int,
newRowConfigurationHandler: RowConfigurationHandler<*, *>?
) {
if (view.layoutManager == null) if (view.layoutManager == null)
view.layoutManager = LinearLayoutManager(view.context, RecyclerView.VERTICAL, false) view.layoutManager = LinearLayoutManager(view.context, RecyclerView.VERTICAL, false)
if (oldList === newList && oldLayoutId == newLayoutId) if (oldList === newList && oldLayoutId == newLayoutId)
@ -123,16 +129,20 @@ object BindingAdapters {
@JvmStatic @JvmStatic
@BindingAdapter("onBeforeCheckedChanged") @BindingAdapter("onBeforeCheckedChanged")
fun setOnBeforeCheckedChanged(view: ToggleSwitch, fun setOnBeforeCheckedChanged(
listener: OnBeforeCheckedChangeListener?) { view: ToggleSwitch,
listener: OnBeforeCheckedChangeListener?
) {
view.setOnBeforeCheckedChangeListener(listener) view.setOnBeforeCheckedChangeListener(listener)
} }
@JvmStatic @JvmStatic
@BindingAdapter("onFocusChange") @BindingAdapter("onFocusChange")
fun setOnFocusChange(view: EditText, fun setOnFocusChange(
listener: View.OnFocusChangeListener?) { view: EditText,
view.setOnFocusChangeListener(listener) listener: View.OnFocusChangeListener?
) {
view.onFocusChangeListener = listener
} }
@JvmStatic @JvmStatic

View File

@ -61,8 +61,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
} }
} }
override fun onItemRangeChanged(sender: ObservableList<T>, positionStart: Int, override fun onItemRangeChanged(
itemCount: Int) { sender: ObservableList<T>, positionStart: Int,
itemCount: Int
) {
val listener = weakListener.get() val listener = weakListener.get()
if (listener != null) { if (listener != null) {
for (i in positionStart until positionStart + itemCount) { for (i in positionStart until positionStart + itemCount) {
@ -75,8 +77,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
} }
} }
override fun onItemRangeInserted(sender: ObservableList<T>, positionStart: Int, override fun onItemRangeInserted(
itemCount: Int) { sender: ObservableList<T>, positionStart: Int,
itemCount: Int
) {
val listener = weakListener.get() val listener = weakListener.get()
if (listener != null) { if (listener != null) {
for (i in positionStart until positionStart + itemCount) for (i in positionStart until positionStart + itemCount)
@ -86,8 +90,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
} }
} }
override fun onItemRangeMoved(sender: ObservableList<T>, fromPosition: Int, override fun onItemRangeMoved(
toPosition: Int, itemCount: Int) { sender: ObservableList<T>, fromPosition: Int,
toPosition: Int, itemCount: Int
) {
val listener = weakListener.get() val listener = weakListener.get()
if (listener != null) { if (listener != null) {
val views = arrayOfNulls<View>(itemCount) val views = arrayOfNulls<View>(itemCount)
@ -99,8 +105,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
} }
} }
override fun onItemRangeRemoved(sender: ObservableList<T>, positionStart: Int, override fun onItemRangeRemoved(
itemCount: Int) { sender: ObservableList<T>, positionStart: Int,
itemCount: Int
) {
val listener = weakListener.get() val listener = weakListener.get()
if (listener != null) { if (listener != null) {
listener.container.removeViews(positionStart, itemCount) listener.container.removeViews(positionStart, itemCount)

View File

@ -49,7 +49,8 @@ class AppListDialogFragment : DialogFragment() {
packageInfos.forEach { packageInfos.forEach {
val packageName = it.packageName val packageName = it.packageName
val appInfo = it.applicationInfo val appInfo = it.applicationInfo
val appData = ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName)) val appData =
ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
applicationData.add(appData) applicationData.add(appData)
appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) { override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
@ -143,10 +144,12 @@ class AppListDialogFragment : DialogFragment() {
selectedApps.add(data.packageName) selectedApps.add(data.packageName)
} }
} }
setFragmentResult(REQUEST_SELECTION, bundleOf( setFragmentResult(
REQUEST_SELECTION, bundleOf(
KEY_SELECTED_APPS to selectedApps.toTypedArray(), KEY_SELECTED_APPS to selectedApps.toTypedArray(),
KEY_IS_EXCLUDED to (tabs?.selectedTabPosition == 0) KEY_IS_EXCLUDED to (tabs?.selectedTabPosition == 0)
)) )
)
dismiss() dismiss()
} }

View File

@ -99,8 +99,8 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
val view = view val view = view
if (view != null) if (view != null)
Snackbar.make(view, message, Snackbar.LENGTH_LONG) Snackbar.make(view, message, Snackbar.LENGTH_LONG)
.setAnchorView(view.findViewById(R.id.create_fab)) .setAnchorView(view.findViewById(R.id.create_fab))
.show() .show()
else else
Toast.makeText(activity, message, Toast.LENGTH_LONG).show() Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
Log.e(TAG, message, e) Log.e(TAG, message, e)

View File

@ -37,6 +37,7 @@ class ConfigNamingDialogFragment : DialogFragment() {
} }
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val configText = requireArguments().getString(KEY_CONFIG_TEXT) val configText = requireArguments().getString(KEY_CONFIG_TEXT)

View File

@ -40,8 +40,10 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
menuInflater.inflate(R.menu.tunnel_detail, menu) menuInflater.inflate(R.menu.tunnel_detail, menu)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(
savedInstanceState: Bundle?): View? { inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false) binding = TunnelDetailFragmentBinding.inflate(inflater, container, false)
binding?.executePendingBindings() binding?.executePendingBindings()
@ -110,16 +112,18 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
val statistics = tunnel.getStatisticsAsync() val statistics = tunnel.getStatisticsAsync()
for (i in 0 until binding.peersLayout.childCount) { for (i in 0 until binding.peersLayout.childCount) {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)) val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
?: continue ?: continue
val publicKey = peer.item!!.publicKey val publicKey = peer.item!!.publicKey
val peerStats = statistics.peer(publicKey) val peerStats = statistics.peer(publicKey)
if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) { if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) {
peer.transferLabel.visibility = View.GONE peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE peer.transferText.visibility = View.GONE
} else { } else {
peer.transferText.text = getString(R.string.transfer_rx_tx, peer.transferText.text = getString(
R.string.transfer_rx_tx,
QuantityFormatter.formatBytes(peerStats.rxBytes), QuantityFormatter.formatBytes(peerStats.rxBytes),
QuantityFormatter.formatBytes(peerStats.txBytes)) QuantityFormatter.formatBytes(peerStats.txBytes)
)
peer.transferLabel.visibility = View.VISIBLE peer.transferLabel.visibility = View.VISIBLE
peer.transferText.visibility = View.VISIBLE peer.transferText.visibility = View.VISIBLE
} }
@ -135,7 +139,7 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
} catch (e: Throwable) { } catch (e: Throwable) {
for (i in 0 until binding.peersLayout.childCount) { for (i in 0 until binding.peersLayout.childCount) {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)) val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
?: continue ?: continue
peer.transferLabel.visibility = View.GONE peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE peer.transferText.visibility = View.GONE
peer.latestHandshakeLabel.visibility = View.GONE peer.latestHandshakeLabel.visibility = View.GONE

View File

@ -5,7 +5,6 @@
package com.wireguard.android.fragment package com.wireguard.android.fragment
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.util.Log import android.util.Log
@ -67,16 +66,14 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
} }
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.config_editor, menu) menuInflater.inflate(R.menu.config_editor, menu)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(
savedInstanceState: Bundle?): View? { inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false) binding = TunnelEditorFragmentBinding.inflate(inflater, container, false)
binding?.apply { binding?.apply {
@ -103,8 +100,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
val focusedView = activity.currentFocus val focusedView = activity.currentFocus
if (focusedView != null) { if (focusedView != null) {
val inputManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager val inputManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
inputManager?.hideSoftInputFromWindow(focusedView.windowToken, inputManager?.hideSoftInputFromWindow(
InputMethodManager.HIDE_NOT_ALWAYS) focusedView.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
} }
parentFragmentManager.popBackStackImmediate() parentFragmentManager.popBackStackImmediate()
@ -138,6 +137,7 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
onTunnelCreated(null, e) onTunnelCreated(null, e)
} }
} }
tunnel!!.name != binding!!.name -> { tunnel!!.name != binding!!.name -> {
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name) Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
try { try {
@ -147,6 +147,7 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
onTunnelRenamed(tunnel!!, newConfig, e) onTunnelRenamed(tunnel!!, newConfig, e)
} }
} }
else -> { else -> {
Log.d(TAG, "Attempting to save config of " + tunnel!!.name) Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
try { try {
@ -202,8 +203,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, override fun onSelectedTunnelChanged(
newTunnel: ObservableTunnel?) { oldTunnel: ObservableTunnel?,
newTunnel: ObservableTunnel?
) {
tunnel = newTunnel tunnel = newTunnel
if (binding == null) return if (binding == null) return
binding!!.config = ConfigProxy() binding!!.config = ConfigProxy()
@ -240,8 +243,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
} }
} }
private suspend fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config, private suspend fun onTunnelRenamed(
throwable: Throwable?) { renamedTunnel: ObservableTunnel, newConfig: Config,
throwable: Throwable?
) {
val ctx = activity ?: Application.get() val ctx = activity ?: Application.get()
if (throwable == null) { if (throwable == null) {
val message = ctx.getString(R.string.tunnel_rename_success, renamedTunnel.name) val message = ctx.getString(R.string.tunnel_rename_success, renamedTunnel.name)
@ -298,13 +303,15 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
haveShownKeys = true haveShownKeys = true
showPrivateKey(edit) showPrivateKey(edit)
} }
is BiometricAuthenticator.Result.Failure -> { is BiometricAuthenticator.Result.Failure -> {
Snackbar.make( Snackbar.make(
binding!!.mainContainer, binding!!.mainContainer,
it.message, it.message,
Snackbar.LENGTH_SHORT Snackbar.LENGTH_SHORT
).show() ).show()
} }
is BiometricAuthenticator.Result.Cancelled -> {} is BiometricAuthenticator.Result.Cancelled -> {}
} }
} }

View File

@ -91,8 +91,10 @@ class TunnelListFragment : BaseFragment() {
} }
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(
savedInstanceState: Bundle?): View? { inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
binding = TunnelListFragmentBinding.inflate(inflater, container, false) binding = TunnelListFragmentBinding.inflate(inflater, container, false)
val bottomSheet = AddTunnelsSheet() val bottomSheet = AddTunnelsSheet()
@ -105,14 +107,18 @@ class TunnelListFragment : BaseFragment() {
AddTunnelsSheet.REQUEST_CREATE -> { AddTunnelsSheet.REQUEST_CREATE -> {
startActivity(Intent(requireActivity(), TunnelCreatorActivity::class.java)) startActivity(Intent(requireActivity(), TunnelCreatorActivity::class.java))
} }
AddTunnelsSheet.REQUEST_IMPORT -> { AddTunnelsSheet.REQUEST_IMPORT -> {
tunnelFileImportResultLauncher.launch("*/*") tunnelFileImportResultLauncher.launch("*/*")
} }
AddTunnelsSheet.REQUEST_SCAN -> { AddTunnelsSheet.REQUEST_SCAN -> {
qrImportResultLauncher.launch(ScanOptions() qrImportResultLauncher.launch(
ScanOptions()
.setOrientationLocked(false) .setOrientationLocked(false)
.setBeepEnabled(false) .setBeepEnabled(false)
.setPrompt(getString(R.string.qr_code_hint))) .setPrompt(getString(R.string.qr_code_hint))
)
} }
} }
} }
@ -191,8 +197,8 @@ class TunnelListFragment : BaseFragment() {
val binding = binding val binding = binding
if (binding != null) if (binding != null)
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG) Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG)
.setAnchorView(binding.createFab) .setAnchorView(binding.createFab)
.show() .show()
else else
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show() Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
} }
@ -234,6 +240,7 @@ class TunnelListFragment : BaseFragment() {
mode.finish() mode.finish()
true true
} }
R.id.menu_action_select_all -> { R.id.menu_action_select_all -> {
lifecycleScope.launch { lifecycleScope.launch {
val tunnels = Application.getTunnelManager().getTunnels() val tunnels = Application.getTunnelManager().getTunnels()
@ -243,6 +250,7 @@ class TunnelListFragment : BaseFragment() {
} }
true true
} }
else -> false else -> false
} }
} }
@ -308,7 +316,7 @@ class TunnelListFragment : BaseFragment() {
private fun animateFab(view: View?, show: Boolean) { private fun animateFab(view: View?, show: Boolean) {
view ?: return view ?: return
val animation = AnimationUtils.loadAnimation( val animation = AnimationUtils.loadAnimation(
context, if (show) R.anim.scale_up else R.anim.scale_down context, if (show) R.anim.scale_up else R.anim.scale_down
) )
animation.setAnimationListener(object : Animation.AnimationListener { animation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) { override fun onAnimationRepeat(animation: Animation?) {

View File

@ -21,10 +21,10 @@ import kotlinx.coroutines.withContext
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel. * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
*/ */
class ObservableTunnel internal constructor( class ObservableTunnel internal constructor(
private val manager: TunnelManager, private val manager: TunnelManager,
private var name: String, private var name: String,
config: Config?, config: Config?,
state: Tunnel.State state: Tunnel.State
) : BaseObservable(), Keyed<String>, Tunnel { ) : BaseObservable(), Keyed<String>, Tunnel {
override val key override val key
get() = name get() = name

View File

@ -140,7 +140,8 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
if (previouslyRunning.isEmpty()) return if (previouslyRunning.isEmpty()) return
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll() tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }
.awaitAll()
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
} }

View File

@ -70,14 +70,16 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
val message = context.getString(R.string.zip_export_error, error) val message = context.getString(R.string.zip_export_error, error)
Log.e(TAG, message, e) Log.e(TAG, message, e)
Snackbar.make( Snackbar.make(
activity.findViewById(android.R.id.content), activity.findViewById(android.R.id.content),
message, Snackbar.LENGTH_LONG).show() message, Snackbar.LENGTH_LONG
).show()
isEnabled = true isEnabled = true
} }
} }
} }
override fun getSummary() = if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath) override fun getSummary() =
if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)
override fun getTitle() = context.getString(R.string.zip_export_title) override fun getTitle() = context.getString(R.string.zip_export_title)
@ -91,13 +93,15 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
isEnabled = false isEnabled = false
exportZip() exportZip()
} }
is BiometricAuthenticator.Result.Failure -> { is BiometricAuthenticator.Result.Failure -> {
Snackbar.make( Snackbar.make(
activity.findViewById(android.R.id.content), activity.findViewById(android.R.id.content),
it.message, it.message,
Snackbar.LENGTH_SHORT Snackbar.LENGTH_SHORT
).show() ).show()
} }
is BiometricAuthenticator.Result.Cancelled -> {} is BiometricAuthenticator.Result.Cancelled -> {}
} }
} }

View File

@ -42,8 +42,7 @@ object SnackbarUpdateShower {
override fun onDismissed(snackbar: Snackbar?, @DismissEvent event: Int) { override fun onDismissed(snackbar: Snackbar?, @DismissEvent event: Int) {
super.onDismissed(snackbar, event) super.onDismissed(snackbar, event)
if (event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_ACTION || if (event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_ACTION ||
(snackbar == actionSnackbar && !showingAction) || (snackbar == actionSnackbar && !showingAction) || (snackbar == statusSnackbar && !showingStatus)
(snackbar == statusSnackbar && !showingStatus)
) )
return return
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
@ -106,10 +105,7 @@ object SnackbarUpdateShower {
snackbar.dismiss() snackbar.dismiss()
is Updater.Progress.Available -> is Updater.Progress.Available ->
snackbar.showAction( snackbar.showAction(context.getString(R.string.updater_avalable), context.getString(R.string.updater_action)) {
context.getString(R.string.updater_avalable),
context.getString(R.string.updater_action)
) {
progress.update() progress.update()
} }
@ -145,12 +141,7 @@ object SnackbarUpdateShower {
} }
is Updater.Progress.Failure -> { is Updater.Progress.Failure -> {
snackbar.showText( snackbar.showText( context.getString(R.string.updater_failure, ErrorMessages[progress.error]))
context.getString(
R.string.updater_failure,
ErrorMessages[progress.error]
)
)
delay(5.seconds) delay(5.seconds)
progress.retry() progress.retry()
} }

View File

@ -44,13 +44,11 @@ import kotlin.time.Duration.Companion.seconds
object Updater { object Updater {
private const val TAG = "WireGuard/Updater" private const val TAG = "WireGuard/Updater"
private const val LATEST_VERSION_URL = private const val LATEST_VERSION_URL = "https://download.wireguard.com/android-client/latest.sig"
"https://download.wireguard.com/android-client/latest.sig"
private const val APK_PATH_URL = "https://download.wireguard.com/android-client/%s" private const val APK_PATH_URL = "https://download.wireguard.com/android-client/%s"
private val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID.removeSuffix(".debug") + "-" private val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID.removeSuffix(".debug") + "-"
private const val APK_NAME_SUFFIX = ".apk" private const val APK_NAME_SUFFIX = ".apk"
private const val RELEASE_PUBLIC_KEY_BASE64 = private const val RELEASE_PUBLIC_KEY_BASE64 = "RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
"RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
private val CURRENT_VERSION = Version(BuildConfig.VERSION_NAME.removeSuffix("-debug")) private val CURRENT_VERSION = Version(BuildConfig.VERSION_NAME.removeSuffix("-debug"))
private val updaterScope = CoroutineScope(Job() + Dispatchers.IO) private val updaterScope = CoroutineScope(Job() + Dispatchers.IO)
@ -219,12 +217,7 @@ object Updater {
val receiver = InstallReceiver() val receiver = InstallReceiver()
val context = Application.get().applicationContext val context = Application.get().applicationContext
val pendingIntent = withContext(Dispatchers.Main) { val pendingIntent = withContext(Dispatchers.Main) {
ContextCompat.registerReceiver( ContextCompat.registerReceiver(context, receiver, IntentFilter(receiver.sessionId), ContextCompat.RECEIVER_NOT_EXPORTED)
context,
receiver,
IntentFilter(receiver.sessionId),
ContextCompat.RECEIVER_NOT_EXPORTED
)
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
context, context,
0, 0,
@ -241,24 +234,20 @@ object Updater {
} }
emitProgress(Progress.Downloading(0UL, 0UL), true) emitProgress(Progress.Downloading(0UL, 0UL), true)
val connection = val connection = URL(APK_PATH_URL.format(update.fileName)).openConnection() as HttpURLConnection
URL(APK_PATH_URL.format(update.fileName)).openConnection() as HttpURLConnection
connection.setRequestProperty("User-Agent", Application.USER_AGENT) connection.setRequestProperty("User-Agent", Application.USER_AGENT)
connection.connect() connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) if (connection.responseCode != HttpURLConnection.HTTP_OK)
throw IOException("Update could not be fetched: ${connection.responseCode}") throw IOException("Update could not be fetched: ${connection.responseCode}")
var downloadedByteLen: ULong = 0UL var downloadedByteLen: ULong = 0UL
val totalByteLen = val totalByteLen = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) connection.contentLengthLong else connection.contentLength).toLong().toULong()
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) connection.contentLengthLong else connection.contentLength).toLong()
.toULong()
val fileBytes = ByteArray(1024 * 32 /* 32 KiB */) val fileBytes = ByteArray(1024 * 32 /* 32 KiB */)
val digest = MessageDigest.getInstance("SHA-256") val digest = MessageDigest.getInstance("SHA-256")
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true) emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
val installer = context.packageManager.packageInstaller val installer = context.packageManager.packageInstaller
val params = val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
params.setAppPackageName(context.packageName) /* Enforces updates; disallows new apps. */ params.setAppPackageName(context.packageName) /* Enforces updates; disallows new apps. */
@ -316,18 +305,10 @@ object Updater {
if (sessionId != intent.action) if (sessionId != intent.action)
return return
when (val status = when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE_INVALID)) {
intent.getIntExtra(
PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE_INVALID
)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> { PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0) val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
val userIntervention = IntentCompat.getParcelableExtra( val userIntervention = IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java)!!
intent,
Intent.EXTRA_INTENT,
Intent::class.java
)!!
Application.getCoroutineScope().launch { Application.getCoroutineScope().launch {
emitProgress(Progress.NeedsUserIntervention(userIntervention, id)) emitProgress(Progress.NeedsUserIntervention(userIntervention, id))
} }
@ -346,9 +327,7 @@ object Updater {
context.applicationContext.packageManager.packageInstaller.abandonSession(id) context.applicationContext.packageManager.packageInstaller.abandonSession(id)
} catch (_: SecurityException) { } catch (_: SecurityException) {
} }
val message = val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "Installation error $status"
intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
?: "Installation error $status"
Application.getCoroutineScope().launch { Application.getCoroutineScope().launch {
val e = Exception(message) val e = Exception(message)
Log.e(TAG, "Update failure", e) Log.e(TAG, "Update failure", e)
@ -365,9 +344,7 @@ object Updater {
return return
updaterScope.launch { updaterScope.launch {
if (UserKnobs.updaterNewerVersionSeen.firstOrNull() if (UserKnobs.updaterNewerVersionSeen.firstOrNull()?.let { Version(it) > CURRENT_VERSION } == true)
?.let { Version(it) > CURRENT_VERSION } == true
)
return@launch return@launch
var waitTime = 15 var waitTime = 15
@ -387,8 +364,10 @@ object Updater {
} }
UserKnobs.updaterNewerVersionSeen.onEach { ver -> UserKnobs.updaterNewerVersionSeen.onEach { ver ->
if (ver != null && Version(ver) > CURRENT_VERSION && UserKnobs.updaterNewerVersionConsented.firstOrNull() if (
?.let { Version(it) > CURRENT_VERSION } != true ver != null &&
Version(ver) > CURRENT_VERSION &&
UserKnobs.updaterNewerVersionConsented.firstOrNull()?.let { Version(it) > CURRENT_VERSION } != true
) )
emitProgress(Progress.Available(ver)) emitProgress(Progress.Available(ver))
}.launchIn(Application.getCoroutineScope()) }.launchIn(Application.getCoroutineScope())

View File

@ -13,5 +13,5 @@ object AdminKnobs {
private val restrictions: RestrictionsManager? = Application.get().getSystemService() private val restrictions: RestrictionsManager? = Application.get().getSystemService()
val disableConfigExport: Boolean val disableConfigExport: Boolean
get() = restrictions?.applicationRestrictions?.getBoolean("disable_config_export", false) get() = restrictions?.applicationRestrictions?.getBoolean("disable_config_export", false)
?: false ?: false
} }

View File

@ -31,25 +31,29 @@ object BiometricAuthenticator {
} }
fun authenticate( fun authenticate(
@StringRes dialogTitleRes: Int, @StringRes dialogTitleRes: Int,
fragment: Fragment, fragment: Fragment,
callback: (Result) -> Unit callback: (Result) -> Unit
) { ) {
val authCallback = object : BiometricPrompt.AuthenticationCallback() { val authCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString) super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString") Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString")
callback(when (errorCode) { callback(
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, when (errorCode) {
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> { BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
Result.Cancelled BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
Result.Cancelled
}
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
Result.HardwareUnavailableOrDisabled
}
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
} }
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE, )
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
Result.HardwareUnavailableOrDisabled
}
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
})
} }
override fun onAuthenticationFailed() { override fun onAuthenticationFailed() {
@ -64,9 +68,9 @@ object BiometricAuthenticator {
} }
val biometricPrompt = BiometricPrompt(fragment, { Handler(Looper.getMainLooper()).post(it) }, authCallback) val biometricPrompt = BiometricPrompt(fragment, { Handler(Looper.getMainLooper()).post(it) }, authCallback)
val promptInfo = BiometricPrompt.PromptInfo.Builder() val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(fragment.getString(dialogTitleRes)) .setTitle(fragment.getString(dialogTitleRes))
.setAllowedAuthenticators(allowedAuthenticators) .setAllowedAuthenticators(allowedAuthenticators)
.build() .build()
if (BiometricManager.from(fragment.requireContext()).canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS) { if (BiometricManager.from(fragment.requireContext()).canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS) {
biometricPrompt.authenticate(promptInfo) biometricPrompt.authenticate(promptInfo)
} else { } else {

View File

@ -46,9 +46,9 @@ class DownloadsFileSaver(private val context: ComponentActivity) {
contentValues.put(MediaColumns.DISPLAY_NAME, name) contentValues.put(MediaColumns.DISPLAY_NAME, name)
contentValues.put(MediaColumns.MIME_TYPE, mimeType) contentValues.put(MediaColumns.MIME_TYPE, mimeType)
val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
?: throw IOException(context.getString(R.string.create_downloads_file_error)) ?: throw IOException(context.getString(R.string.create_downloads_file_error))
val contentStream = contentResolver.openOutputStream(contentUri) val contentStream = contentResolver.openOutputStream(contentUri)
?: throw IOException(context.getString(R.string.create_downloads_file_error)) ?: throw IOException(context.getString(R.string.create_downloads_file_error))
@Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null) @Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null)
var path: String? = null var path: String? = null
if (cursor != null) { if (cursor != null) {

View File

@ -22,47 +22,47 @@ import java.net.InetAddress
object ErrorMessages { object ErrorMessages {
private val BCE_REASON_MAP = mapOf( private val BCE_REASON_MAP = mapOf(
BadConfigException.Reason.INVALID_KEY to R.string.bad_config_reason_invalid_key, BadConfigException.Reason.INVALID_KEY to R.string.bad_config_reason_invalid_key,
BadConfigException.Reason.INVALID_NUMBER to R.string.bad_config_reason_invalid_number, BadConfigException.Reason.INVALID_NUMBER to R.string.bad_config_reason_invalid_number,
BadConfigException.Reason.INVALID_VALUE to R.string.bad_config_reason_invalid_value, BadConfigException.Reason.INVALID_VALUE to R.string.bad_config_reason_invalid_value,
BadConfigException.Reason.MISSING_ATTRIBUTE to R.string.bad_config_reason_missing_attribute, BadConfigException.Reason.MISSING_ATTRIBUTE to R.string.bad_config_reason_missing_attribute,
BadConfigException.Reason.MISSING_SECTION to R.string.bad_config_reason_missing_section, BadConfigException.Reason.MISSING_SECTION to R.string.bad_config_reason_missing_section,
BadConfigException.Reason.SYNTAX_ERROR to R.string.bad_config_reason_syntax_error, BadConfigException.Reason.SYNTAX_ERROR to R.string.bad_config_reason_syntax_error,
BadConfigException.Reason.UNKNOWN_ATTRIBUTE to R.string.bad_config_reason_unknown_attribute, BadConfigException.Reason.UNKNOWN_ATTRIBUTE to R.string.bad_config_reason_unknown_attribute,
BadConfigException.Reason.UNKNOWN_SECTION to R.string.bad_config_reason_unknown_section BadConfigException.Reason.UNKNOWN_SECTION to R.string.bad_config_reason_unknown_section
) )
private val BE_REASON_MAP = mapOf( private val BE_REASON_MAP = mapOf(
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error, BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error,
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error, BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error, BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error,
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error, BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error, BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error, BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error,
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error, BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error,
BackendException.Reason.DNS_RESOLUTION_FAILURE to R.string.tunnel_dns_failure BackendException.Reason.DNS_RESOLUTION_FAILURE to R.string.tunnel_dns_failure
) )
private val KFE_FORMAT_MAP = mapOf( private val KFE_FORMAT_MAP = mapOf(
Key.Format.BASE64 to R.string.key_length_explanation_base64, Key.Format.BASE64 to R.string.key_length_explanation_base64,
Key.Format.BINARY to R.string.key_length_explanation_binary, Key.Format.BINARY to R.string.key_length_explanation_binary,
Key.Format.HEX to R.string.key_length_explanation_hex Key.Format.HEX to R.string.key_length_explanation_hex
) )
private val KFE_TYPE_MAP = mapOf( private val KFE_TYPE_MAP = mapOf(
KeyFormatException.Type.CONTENTS to R.string.key_contents_error, KeyFormatException.Type.CONTENTS to R.string.key_contents_error,
KeyFormatException.Type.LENGTH to R.string.key_length_error KeyFormatException.Type.LENGTH to R.string.key_length_error
) )
private val PE_CLASS_MAP = mapOf( private val PE_CLASS_MAP = mapOf(
InetAddress::class.java to R.string.parse_error_inet_address, InetAddress::class.java to R.string.parse_error_inet_address,
InetEndpoint::class.java to R.string.parse_error_inet_endpoint, InetEndpoint::class.java to R.string.parse_error_inet_endpoint,
InetNetwork::class.java to R.string.parse_error_inet_network, InetNetwork::class.java to R.string.parse_error_inet_network,
Int::class.java to R.string.parse_error_integer Int::class.java to R.string.parse_error_integer
) )
private val RSE_REASON_MAP = mapOf( private val RSE_REASON_MAP = mapOf(
RootShellException.Reason.NO_ROOT_ACCESS to R.string.error_root, RootShellException.Reason.NO_ROOT_ACCESS to R.string.error_root,
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR to R.string.shell_marker_count_error, RootShellException.Reason.SHELL_MARKER_COUNT_ERROR to R.string.shell_marker_count_error,
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR to R.string.shell_exit_status_read_error, RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR to R.string.shell_exit_status_read_error,
RootShellException.Reason.SHELL_START_ERROR to R.string.shell_start_error, RootShellException.Reason.SHELL_START_ERROR to R.string.shell_start_error,
RootShellException.Reason.CREATE_BIN_DIR_ERROR to R.string.create_bin_dir_error, RootShellException.Reason.CREATE_BIN_DIR_ERROR to R.string.create_bin_dir_error,
RootShellException.Reason.CREATE_TEMP_DIR_ERROR to R.string.create_temp_dir_error RootShellException.Reason.CREATE_TEMP_DIR_ERROR to R.string.create_temp_dir_error
) )
operator fun get(throwable: Throwable?): String { operator fun get(throwable: Throwable?): String {
@ -80,21 +80,27 @@ object ErrorMessages {
val explanation = getBadConfigExceptionExplanation(resources, rootCause) val explanation = getBadConfigExceptionExplanation(resources, rootCause)
resources.getString(R.string.bad_config_error, reason, context) + explanation resources.getString(R.string.bad_config_error, reason, context) + explanation
} }
rootCause is BackendException -> { rootCause is BackendException -> {
resources.getString(BE_REASON_MAP.getValue(rootCause.reason), *rootCause.format) resources.getString(BE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
} }
rootCause is RootShellException -> { rootCause is RootShellException -> {
resources.getString(RSE_REASON_MAP.getValue(rootCause.reason), *rootCause.format) resources.getString(RSE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
} }
rootCause is NotFoundException -> { rootCause is NotFoundException -> {
resources.getString(R.string.error_no_qr_found) resources.getString(R.string.error_no_qr_found)
} }
rootCause is ChecksumException -> { rootCause is ChecksumException -> {
resources.getString(R.string.error_qr_checksum) resources.getString(R.string.error_qr_checksum)
} }
rootCause.localizedMessage != null -> { rootCause.localizedMessage != null -> {
rootCause.localizedMessage!! rootCause.localizedMessage!!
} }
else -> { else -> {
val errorType = rootCause.javaClass.simpleName val errorType = rootCause.javaClass.simpleName
resources.getString(R.string.generic_error, errorType) resources.getString(R.string.generic_error, errorType)
@ -102,8 +108,10 @@ object ErrorMessages {
} }
} }
private fun getBadConfigExceptionExplanation(resources: Resources, private fun getBadConfigExceptionExplanation(
bce: BadConfigException): String { resources: Resources,
bce: BadConfigException
): String {
if (bce.cause is KeyFormatException) { if (bce.cause is KeyFormatException) {
val kfe = bce.cause as KeyFormatException? val kfe = bce.cause as KeyFormatException?
if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format)) if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format))
@ -120,8 +128,10 @@ object ErrorMessages {
return "" return ""
} }
private fun getBadConfigExceptionReason(resources: Resources, private fun getBadConfigExceptionReason(
bce: BadConfigException): String { resources: Resources,
bce: BadConfigException
): String {
if (bce.cause is KeyFormatException) { if (bce.cause is KeyFormatException) {
val kfe = bce.cause as KeyFormatException? val kfe = bce.cause as KeyFormatException?
return resources.getString(KFE_TYPE_MAP.getValue(kfe!!.type)) return resources.getString(KFE_TYPE_MAP.getValue(kfe!!.type))
@ -137,7 +147,8 @@ object ErrorMessages {
var cause = throwable var cause = throwable
while (cause.cause != null) { while (cause.cause != null) {
if (cause is BadConfigException || cause is BackendException || if (cause is BadConfigException || cause is BackendException ||
cause is RootShellException) break cause is RootShellException
) break
val nextCause = cause.cause!! val nextCause = cause.cause!!
if (nextCause is RemoteException) break if (nextCause is RemoteException) break
cause = nextCause cause = nextCause

View File

@ -25,7 +25,7 @@ val Any.applicationScope: CoroutineScope
val Preference.activity: SettingsActivity val Preference.activity: SettingsActivity
get() = context as? SettingsActivity get() = context as? SettingsActivity
?: throw IllegalStateException("Failed to resolve SettingsActivity") ?: throw IllegalStateException("Failed to resolve SettingsActivity")
val Preference.lifecycleScope: CoroutineScope val Preference.lifecycleScope: CoroutineScope
get() = activity.lifecycleScope get() = activity.lifecycleScope

View File

@ -55,11 +55,13 @@ class QrCodeFromFileScanner(
multFactor = originalWidth.toFloat() / originalHeight.toFloat() multFactor = originalWidth.toFloat() / originalHeight.toFloat()
newWidth = (newHeight * multFactor).toInt() newWidth = (newHeight * multFactor).toInt()
} }
originalWidth > originalHeight -> { originalWidth > originalHeight -> {
newWidth = scaledSize newWidth = scaledSize
multFactor = originalHeight.toFloat() / originalWidth.toFloat() multFactor = originalHeight.toFloat() / originalWidth.toFloat()
newHeight = (newWidth * multFactor).toInt() newHeight = (newWidth * multFactor).toInt()
} }
originalHeight == originalWidth -> { originalHeight == originalWidth -> {
newHeight = scaledSize newHeight = scaledSize
newWidth = scaledSize newWidth = scaledSize

View File

@ -15,7 +15,6 @@ import com.wireguard.android.Application
import com.wireguard.android.R import com.wireguard.android.R
import java.util.Locale import java.util.Locale
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
object QuantityFormatter { object QuantityFormatter {
fun formatBytes(bytes: Long): String { fun formatBytes(bytes: Long): String {

View File

@ -24,7 +24,6 @@ import java.io.BufferedReader
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.ArrayList
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -135,12 +134,16 @@ object TunnelImporter {
message = context.getString(R.string.import_success, tunnels[0].name) message = context.getString(R.string.import_success, tunnels[0].name)
else if (tunnels.isEmpty() && throwables.size == 1) else if (tunnels.isEmpty() && throwables.size == 1)
else if (throwables.isEmpty()) else if (throwables.isEmpty())
message = context.resources.getQuantityString(R.plurals.import_total_success, message = context.resources.getQuantityString(
tunnels.size, tunnels.size) R.plurals.import_total_success,
tunnels.size, tunnels.size
)
else if (!throwables.isEmpty()) else if (!throwables.isEmpty())
message = context.resources.getQuantityString(R.plurals.import_partial_success, message = context.resources.getQuantityString(
tunnels.size + throwables.size, R.plurals.import_partial_success,
tunnels.size, tunnels.size + throwables.size) tunnels.size + throwables.size,
tunnels.size, tunnels.size + throwables.size
)
messageCallback(message) messageCallback(message)
} }

View File

@ -55,9 +55,9 @@ class ConfigProxy : Parcelable {
val resolvedPeers: MutableCollection<Peer> = ArrayList() val resolvedPeers: MutableCollection<Peer> = ArrayList()
peers.forEach { resolvedPeers.add(it.resolve()) } peers.forEach { resolvedPeers.add(it.resolve()) }
return Config.Builder() return Config.Builder()
.setInterface(`interface`.resolve()) .setInterface(`interface`.resolve())
.addPeers(resolvedPeers) .addPeers(resolvedPeers)
.build() .build()
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {

View File

@ -16,8 +16,6 @@ import com.wireguard.config.Attribute
import com.wireguard.config.BadConfigException import com.wireguard.config.BadConfigException
import com.wireguard.config.Peer import com.wireguard.config.Peer
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.ArrayList
import java.util.LinkedHashSet
class PeerProxy : BaseObservable, Parcelable { class PeerProxy : BaseObservable, Parcelable {
private val dnsRoutes: MutableList<String?> = ArrayList() private val dnsRoutes: MutableList<String?> = ArrayList()
@ -240,24 +238,32 @@ class PeerProxy : BaseObservable, Parcelable {
peerProxy.setTotalPeers(sender.size) peerProxy.setTotalPeers(sender.size)
} }
override fun onItemRangeChanged(sender: ObservableList<PeerProxy?>, override fun onItemRangeChanged(
positionStart: Int, itemCount: Int) { sender: ObservableList<PeerProxy?>,
positionStart: Int, itemCount: Int
) {
// Do nothing. // Do nothing.
} }
override fun onItemRangeInserted(sender: ObservableList<PeerProxy?>, override fun onItemRangeInserted(
positionStart: Int, itemCount: Int) { sender: ObservableList<PeerProxy?>,
positionStart: Int, itemCount: Int
) {
onChanged(sender) onChanged(sender)
} }
override fun onItemRangeMoved(sender: ObservableList<PeerProxy?>, override fun onItemRangeMoved(
fromPosition: Int, toPosition: Int, sender: ObservableList<PeerProxy?>,
itemCount: Int) { fromPosition: Int, toPosition: Int,
itemCount: Int
) {
// Do nothing. // Do nothing.
} }
override fun onItemRangeRemoved(sender: ObservableList<PeerProxy?>, override fun onItemRangeRemoved(
positionStart: Int, itemCount: Int) { sender: ObservableList<PeerProxy?>,
positionStart: Int, itemCount: Int
) {
onChanged(sender) onChanged(sender)
} }
} }
@ -276,12 +282,12 @@ class PeerProxy : BaseObservable, Parcelable {
@JvmField @JvmField
val CREATOR: Parcelable.Creator<PeerProxy> = PeerProxyCreator() val CREATOR: Parcelable.Creator<PeerProxy> = PeerProxyCreator()
private val IPV4_PUBLIC_NETWORKS = setOf( private val IPV4_PUBLIC_NETWORKS = setOf(
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4" "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
) )
private val IPV4_WILDCARD = setOf("0.0.0.0/0") private val IPV4_WILDCARD = setOf("0.0.0.0/0")
} }

View File

@ -13,10 +13,12 @@ import com.wireguard.crypto.Key
* InputFilter for entering WireGuard private/public keys encoded with base64. * InputFilter for entering WireGuard private/public keys encoded with base64.
*/ */
class KeyInputFilter : InputFilter { class KeyInputFilter : InputFilter {
override fun filter(source: CharSequence, override fun filter(
sStart: Int, sEnd: Int, source: CharSequence,
dest: Spanned, sStart: Int, sEnd: Int,
dStart: Int, dEnd: Int): CharSequence? { dest: Spanned,
dStart: Int, dEnd: Int
): CharSequence? {
var replacement: SpannableStringBuilder? = null var replacement: SpannableStringBuilder? = null
var rIndex = 0 var rIndex = 0
val dLength = dest.length val dLength = dest.length
@ -26,8 +28,9 @@ class KeyInputFilter : InputFilter {
// Restrict characters to the base64 character set. // Restrict characters to the base64 character set.
// Ensure adding this character does not push the length over the limit. // Ensure adding this character does not push the length over the limit.
if ((dIndex + 1 < Key.Format.BASE64.length && isAllowed(c) || if ((dIndex + 1 < Key.Format.BASE64.length && isAllowed(c) ||
dIndex + 1 == Key.Format.BASE64.length && c == '=') && dIndex + 1 == Key.Format.BASE64.length && c == '=') &&
dLength + (sIndex - sStart) < Key.Format.BASE64.length) { dLength + (sIndex - sStart) < Key.Format.BASE64.length
) {
++rIndex ++rIndex
} else { } else {
if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd) if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)

View File

@ -11,10 +11,10 @@ import android.widget.RelativeLayout
import com.wireguard.android.R import com.wireguard.android.R
class MultiselectableRelativeLayout @JvmOverloads constructor( class MultiselectableRelativeLayout @JvmOverloads constructor(
context: Context? = null, context: Context? = null,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,
defStyleRes: Int = 0 defStyleRes: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) { ) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) {
private var multiselected = false private var multiselected = false

View File

@ -13,10 +13,12 @@ import com.wireguard.android.backend.Tunnel
* InputFilter for entering WireGuard configuration names (Linux interface names). * InputFilter for entering WireGuard configuration names (Linux interface names).
*/ */
class NameInputFilter : InputFilter { class NameInputFilter : InputFilter {
override fun filter(source: CharSequence, override fun filter(
sStart: Int, sEnd: Int, source: CharSequence,
dest: Spanned, sStart: Int, sEnd: Int,
dStart: Int, dEnd: Int): CharSequence? { dest: Spanned,
dStart: Int, dEnd: Int
): CharSequence? {
var replacement: SpannableStringBuilder? = null var replacement: SpannableStringBuilder? = null
var rIndex = 0 var rIndex = 0
val dLength = dest.length val dLength = dest.length
@ -26,7 +28,8 @@ class NameInputFilter : InputFilter {
// Restrict characters to those valid in interfaces. // Restrict characters to those valid in interfaces.
// Ensure adding this character does not push the length over the limit. // Ensure adding this character does not push the length over the limit.
if (dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c) && if (dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c) &&
dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) { dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH
) {
++rIndex ++rIndex
} else { } else {
if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd) if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)

View File

@ -35,10 +35,10 @@ class SlashDrawable(private val mDrawable: Drawable) : Drawable() {
val radiusX = scale(CORNER_RADIUS, width) val radiusX = scale(CORNER_RADIUS, width)
val radiusY = scale(CORNER_RADIUS, height) val radiusY = scale(CORNER_RADIUS, height)
updateRect( updateRect(
scale(LEFT, width), scale(LEFT, width),
scale(TOP, height), scale(TOP, height),
scale(RIGHT, width), scale(RIGHT, width),
scale(TOP + mCurrentSlashLength, height) scale(TOP + mCurrentSlashLength, height)
) )
mPath.reset() mPath.reset()
// Draw the slash vertically // Draw the slash vertically

View File

@ -8,8 +8,7 @@
app:state_multiselected="true"> app:state_multiselected="true">
<color android:color="?attr/colorSurfaceVariant" /> <color android:color="?attr/colorSurfaceVariant" />
</item> </item>
<item <item android:state_activated="true">
android:state_activated="true">
<color android:color="?attr/colorControlHighlight" /> <color android:color="?attr/colorControlHighlight" />
</item> </item>
</selector> </selector>

View File

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
<import type="com.wireguard.android.widget.NameInputFilter" /> <import type="com.wireguard.android.widget.NameInputFilter" />
</data> </data>
@ -24,7 +25,8 @@
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="textNoSuggestions|textVisiblePassword" android:inputType="textNoSuggestions|textVisiblePassword"
app:filter="@{NameInputFilter.newInstance()}"> app:filter="@{NameInputFilter.newInstance()}">
<requestFocus/>
<requestFocus />
</com.google.android.material.textfield.TextInputEditText> </com.google.android.material.textfield.TextInputEditText>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>

View File

@ -60,11 +60,11 @@
android:src="@mipmap/ic_launcher" /> android:src="@mipmap/ic_launcher" />
<TextView <TextView
android:layout_marginStart="@dimen/tunnel_list_placeholder_margin"
android:layout_marginEnd="@dimen/tunnel_list_placeholder_margin"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="@dimen/tunnel_list_placeholder_margin"
android:layout_marginEnd="@dimen/tunnel_list_placeholder_margin"
android:text="@string/tunnel_list_placeholder" android:text="@string/tunnel_list_placeholder"
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,5 @@
<resources> <resources>
<style name="WireGuardTheme" parent="Theme.Material3.Dark"> <style name="WireGuardTheme" parent="Theme.Material3.Dark">
<item name="colorPrimary">@color/md_theme_dark_primary</item> <item name="colorPrimary">@color/md_theme_dark_primary</item>
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item> <item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="AppTheme" parent="AppThemeBase"> <style name="AppTheme" parent="AppThemeBase">
<item name="android:statusBarColor">?android:colorBackground</item> <item name="android:statusBarColor">?android:colorBackground</item>
<item name="android:windowLightStatusBar">@bool/light_status_bar</item> <item name="android:windowLightStatusBar">@bool/light_status_bar</item>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="AppTheme" parent="AppThemeBase"> <style name="AppTheme" parent="AppThemeBase">
<item name="android:statusBarColor">?android:colorBackground</item> <item name="android:statusBarColor">?android:colorBackground</item>
<item name="android:windowLightStatusBar">@bool/light_status_bar</item> <item name="android:windowLightStatusBar">@bool/light_status_bar</item>

View File

@ -1,5 +1,5 @@
<resources> <resources>
<style name="WireGuardTheme" parent="Theme.Material3.Light"> <style name="WireGuardTheme" parent="Theme.Material3.Light">
<item name="colorPrimary">@color/md_theme_light_primary</item> <item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item> <item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>

View File

@ -40,6 +40,5 @@
android:summaryOff="@string/allow_remote_control_intents_summary_off" android:summaryOff="@string/allow_remote_control_intents_summary_off"
android:summaryOn="@string/allow_remote_control_intents_summary_on" android:summaryOn="@string/allow_remote_control_intents_summary_on"
android:title="@string/allow_remote_control_intents_title" /> android:title="@string/allow_remote_control_intents_title" />
<com.wireguard.android.preference.DonatePreference <com.wireguard.android.preference.DonatePreference android:singleLineTitle="false" />
android:singleLineTitle="false" />
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>