Compare commits

..

50 Commits

Author SHA1 Message Date
Jason A. Donenfeld 914917036e version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 15:52:23 +02:00
Jason A. Donenfeld 6e9ca435ca gradle: trim experimental list
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 13:51:25 +02:00
Jason A. Donenfeld 0797e99559 proguard: cleanup unused parts
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 13:12:02 +02:00
Jason A. Donenfeld 0ba064ff48 build: bump activity-ktx and fragment-ktx
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-25 14:53:41 +02:00
Jason A. Donenfeld b4fe9efadb strings: sync with crowdin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 18:49:36 +02:00
Jason A. Donenfeld b1b08ce716 ui: display latest handshake time
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-24 18:07:03 +02:00
Jason A. Donenfeld 20480992c4 tunnel: export latest handshake stat
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-24 18:06:57 +02:00
Harsh Shandilya 9df90b259a gradle: update wrapper to 8.1.1
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-04-24 11:22:21 +05:30
Jason A. Donenfeld b94a412c09 tools: build elf-cleaner using system compiler
Rather than using the NDK compiler for the host arch and hoping it picks
up on system library locations, just use `cc` and rely on the
traditional symlink to resolve that to the default host compiler.

This should address build errors seen on macOS.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-21 15:43:25 +02:00
Jason A. Donenfeld 0b0a553123 ui: remove unused extra_margin constant
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 19:36:20 +02:00
Jason A. Donenfeld 6d4d2f4ebf tools: ndk-compat: NDK now is minimum API 19
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 19:24:50 +02:00
Jason A. Donenfeld e55358f685 build: bump AGP to 8.0.0
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 14:21:47 +02:00
Jason A. Donenfeld c2fdd0ca93 ui: layout: don't stretch text fields, so that alignment is correct
This makes things look uniform on RTL.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 17:47:36 +02:00
Jason A. Donenfeld 19044b0f53 ui: LogViewerActivity: cleanup ret val
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 16:06:54 +02:00
Jason A. Donenfeld d4e77e6d57 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 15:48:04 +02:00
Jason A. Donenfeld 6ba3e4f001 tools: do not put vcs info into go binary
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 15:48:01 +02:00
Jason A. Donenfeld 8152191277 ui: further reduce log viewer memory usage
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 13:48:55 +02:00
Jason A. Donenfeld 8c6c909e98 tools: bump go
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:52:30 +02:00
Jason A. Donenfeld fdda87fd31 tunnel: defer enabling strict mode until later
Startup code can be synchronous.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:20:26 +02:00
Jason A. Donenfeld 3907f6a4c1 tunnel: do not keep service running when bringing tunnel down
Doing so causes the process to restart when the activity is cleared,
even when no tunnel is running. I fear this might also lead to the
current flurry of DeadSystemExceptions.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:17:55 +02:00
Jason A. Donenfeld 652971d746 tools: clean compile options a bit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 16:23:37 +02:00
Jason A. Donenfeld 1bf8d7e4c4 build: shrink resources
Saves 300k.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-10 18:19:52 +02:00
Jason A. Donenfeld 3c834ac8e3 ui: always show visible localized error messages
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-07 15:22:00 +02:00
Jason A. Donenfeld 8669ae78e1 ui: set app list button text on main thread
Hopefully will fix, "Only the original thread that creted a view
hierarchy can touch its views."

Reported-by: Damir <2k1dmg@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-07 15:18:33 +02:00
Jason A. Donenfeld 53f8c8dbc4 build: remove bespoke signing config properties
Keys should be in HSMs anyway, and a setup like this does not easily
allow for that.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-06 17:16:29 +02:00
Jason A. Donenfeld 1328660b0b build: bump deps
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 22:19:37 +02:00
Jason A. Donenfeld 997edf4e4c version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 17:09:41 +02:00
Jason A. Donenfeld beb40ad09c ui: allow vetoing tunnel selection changes
These changes can happen at the wrong time, so we need to be able to
walk them back. Fixes:

Exception java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  at androidx.fragment.app.FragmentManager.checkStateLoss (FragmentManager.java:1610)
  at androidx.fragment.app.FragmentManager.enqueueAction (FragmentManager.java:1650)
  at androidx.fragment.app.BackStackRecord.commitInternal (BackStackRecord.java:341)
  at androidx.fragment.app.BackStackRecord.commit (BackStackRecord.java:306)
  at androidx.fragment.app.FragmentManagerKt.commit$default (FragmentManager.kt:35)
  at com.wireguard.android.activity.MainActivity.onSelectedTunnelChanged (MainActivity.kt:113)
  at com.wireguard.android.activity.BaseActivity.setSelectedTunnel (BaseActivity.kt:25)
  at com.wireguard.android.activity.BaseActivity$onCreate$1.invokeSuspend (BaseActivity.kt:44)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.internal.DispatchedContinuation.resumeUndispatchedWith (DispatchedContinuation.java:256)
  at kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:177)
  at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.kt:190)
  at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTask.kt:190)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:161)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:397)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:431)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:420)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith (CancellableContinuationImpl.kt:328)
  at kotlinx.coroutines.ResumeAwaitOnCompletion.invoke (JobSupport.kt:1413)
  at kotlinx.coroutines.JobSupport.notifyHandlers (JobSupport.kt:368)
  at kotlinx.coroutines.JobSupport.notifyCompletion (JobSupport.kt:362)
  at kotlinx.coroutines.JobSupport.completeStateFinalization (JobSupport.kt:323)
  at kotlinx.coroutines.JobSupport.finalizeFinishingState (JobSupport.kt:240)
  at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath (JobSupport.kt:906)
  at kotlinx.coroutines.JobSupport.tryMakeCompleting (JobSupport.kt:863)
  at kotlinx.coroutines.JobSupport.makeCompleting$kotlinx_coroutines_core (JobSupport.kt:806)
  at kotlinx.coroutines.CompletableDeferredImpl.complete (CompletableDeferredImpl.java:92)
  at com.wireguard.android.model.TunnelManager$onTunnelsLoaded$1.invokeSuspend (TunnelManager.kt:120)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:237)
  at android.app.ActivityThread.main (ActivityThread.java:8163)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:656)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:967)

Exception java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  at androidx.fragment.app.FragmentManager.checkStateLoss (FragmentManager.java:1610)
  at androidx.fragment.app.FragmentManager.enqueueAction (FragmentManager.java:1650)
  at androidx.fragment.app.BackStackRecord.commitInternal (BackStackRecord.java:341)
  at androidx.fragment.app.BackStackRecord.commit (BackStackRecord.java:306)
  at androidx.fragment.app.FragmentManagerKt.commit$default (FragmentManager.kt:35)
  at com.wireguard.android.activity.MainActivity.onSelectedTunnelChanged (MainActivity.kt:113)
  at com.wireguard.android.activity.BaseActivity.setSelectedTunnel (BaseActivity.kt:25)
  at com.wireguard.android.activity.BaseActivity$onCreate$1.invokeSuspend (BaseActivity.kt:44)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.internal.DispatchedContinuation.resumeUndispatchedWith (DispatchedContinuation.java:256)
  at kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:177)
  at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.kt:190)
  at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTask.kt:190)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:161)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:397)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:431)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:420)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith (CancellableContinuationImpl.kt:328)
  at kotlinx.coroutines.ResumeAwaitOnCompletion.invoke (JobSupport.kt:1413)
  at kotlinx.coroutines.JobSupport.notifyHandlers (JobSupport.kt:368)
  at kotlinx.coroutines.JobSupport.notifyCompletion (JobSupport.kt:362)
  at kotlinx.coroutines.JobSupport.completeStateFinalization (JobSupport.kt:323)
  at kotlinx.coroutines.JobSupport.finalizeFinishingState (JobSupport.kt:240)
  at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath (JobSupport.kt:906)
  at kotlinx.coroutines.JobSupport.tryMakeCompleting (JobSupport.kt:863)
  at kotlinx.coroutines.JobSupport.makeCompleting$kotlinx_coroutines_core (JobSupport.kt:806)
  at kotlinx.coroutines.CompletableDeferredImpl.complete (CompletableDeferredImpl.java:92)
  at com.wireguard.android.model.TunnelManager$onTunnelsLoaded$1.invokeSuspend (TunnelManager.kt:120)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:210)
  at android.os.Looper.loop (Looper.java:299)
  at android.app.ActivityThread.main (ActivityThread.java:8302)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:556)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1037)

Exception java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  at androidx.fragment.app.FragmentManager.checkStateLoss (FragmentManager.java:1610)
  at androidx.fragment.app.FragmentManager.ensureExecReady (FragmentManager.java:1711)
  at androidx.fragment.app.FragmentManager.execPendingActions (FragmentManager.java:1754)
  at androidx.fragment.app.FragmentManager.popBackStackImmediate (FragmentManager.java:854)
  at androidx.fragment.app.FragmentManager.popBackStackImmediate (FragmentManager.java:778)
  at com.wireguard.android.activity.MainActivity.onSelectedTunnelChanged (MainActivity.kt:110)
  at com.wireguard.android.activity.BaseActivity.setSelectedTunnel (BaseActivity.kt:25)
  at com.wireguard.android.activity.BaseActivity$onCreate$1.invokeSuspend (BaseActivity.kt:44)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
  at kotlinx.coroutines.EventLoop.processUnconfinedEvent (EventLoop.common.kt:69)
  at kotlinx.coroutines.DispatchedTaskKt.runUnconfinedEventLoop (DispatchedTask.kt:204)
  at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith (DispatchedContinuation.kt:282)
  at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable (Cancellable.kt:30)
  at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default (Cancellable.kt:25)
  at kotlinx.coroutines.CoroutineStart.invoke (CoroutineStart.java:110)
  at kotlinx.coroutines.AbstractCoroutine.start (AbstractCoroutine.kt:126)
  at kotlinx.coroutines.BuildersKt.launch$default (Builders.kt)
  at com.wireguard.android.model.TunnelManager.onTunnelsLoaded (TunnelManager.java:114)
  at com.wireguard.android.model.TunnelManager.access$onTunnelsLoaded (TunnelManager.java:40)
  at com.wireguard.android.model.TunnelManager$onCreate$1.invokeSuspend (TunnelManager.kt:104)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8757)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 17:08:47 +02:00
Jason A. Donenfeld 4bac284083 ui: don't use primary color on status bar < api 23
The primary color changes in night theme, and since there's no proper
mode for the status icons on those APIs, it looks bad.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 16:20:10 +02:00
Jason A. Donenfeld eae9216fe4 ui: no need to manually recreate activities after night change
Since version 1.1.0, setDefaultNightMode now takes care of that. Also,
set the initial mode in a blocking fashion to prevent flashing white.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 16:03:54 +02:00
Jason A. Donenfeld dc1860c74d ui: distinguish play store installs at runtime for reproducible builds
This change lets us use the same build for F-Droid, Play Store, self
builds, and elsewhere, which makes everything more easily publicly
verifiable, since the build system is reproducible. That means that all
APKs will have the same code and be completely interchangeable, no
matter where they come from.

It does this by removing the build-time branch for special Play Store
builds, and replacing it with a simple runtime check using the
PackageManager APIs that return the name of the installer. If the app is
installed by "com.android.vending", then it's a Play Store install.

It's possible to test this with:

    $ pm install -i com.android.vending path/to/package.apk

And it appears to work well.

One potential concern is that it's unclear whether the Play Store
reviewers install the app using utilities that set com.android.vending
like this. If not, that might be a problem. However, it looks like
various banking apps also use the installer package name check in the
same way, and refuse to start if it's not right. That suggests that it
would be impossible for Play Store reviewers to even review those
banking apps if they did not set com.android.vending properly.

Out of an abundance of caution, though, and in order to avoid a Play
Store suspension that's harder to appeal, I sent a support request
today (which just managed to fit exactly in the 1000 character limit):

    Hi,

    My app pays special attention to Google Play Store guidelines. For that
    reason, there is some code in the app that looks like this:

        if (BuildConfig.IS_GOOGLE_PLAY)
            ...
        else
            ...

    This means that I compile two versions of my app, one for Google Play,
    and another for other app stores. This has worked well for many years
    and it satisfies Google's policy requirements.

    However, compiling two versions of my app is a bit of a pain. Instead, I
    would like to do this check at runtime, with code like this:

        if (pm.getInstallSourceInfo(package).installingPackageName == "com.android.vending")
            ...
        else
            ...

    I have tested that this code works well, and I've installed my app with:

        $ pm install -i com.android.vending ui-release.apk

    This works and successfully satisfies the policy requirements.

    My question is how this works during the review process. Are reviewed
    apps installed with the necessary -i com.android.vending switch to make
    this work?

    Thanks.

They responded fairly quickly:

    Hi Jason,

    Thanks for contacting the Google Play team.

    Unfortunately I'm not able to comment on your planned implementation. If
    you think your app is in compliance, please submit your app for review.
    You may want to review the Developer Program Policies for additional
    policy guidance.

    We recommend reviewing the details listed in this blog post and update
    your app accordingly to comply with the changes.

    Thanks for your understanding and continued support.

    Regards,
    Mia
    Google Play Developer Support

So I'll interpret that as a, "if you think it's okay, submit it and see,
and then we'll let you know." So here we go. Hopefully if it is
rejected, the update will only be blocked, and I'll just revert this
commit and resubmit.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 13:54:23 +02:00
Jason A. Donenfeld 6209d3ee3d tools: clean unwanted ELF flags from binaries
Fixes warnings on old Android, such as:

linker  : /data/data/com.wireguard.android/code_cache/bin/wg-quick: unsupported flags DT_FLAGS_1=0x8000001
linker  : /data/data/com.wireguard.android/code_cache/bin/wg: unsupported flags DT_FLAGS_1=0x8000001

Reported-by: L.W.Reek <syphyr@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 13:50:57 +02:00
Jason A. Donenfeld 33aded09c6 tools: deduplicate cmake arguments
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 13:50:57 +02:00
Jason A. Donenfeld f360034f29 tools: remove cmake dependency hack
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 12:23:41 +02:00
L.W.Reek 01e72064d4 tools: add missing CLOCK_BOOTTIME case to mips64x
Signed-off-by: L.W.Reek <syphyr@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 02:49:26 +02:00
Jason A. Donenfeld 91a75b681d ui: always use MaterialAlertDialogBuilder
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 21:41:49 +02:00
Jason A. Donenfeld 0efdf0cdd7 strings: sync with crowdin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:54:29 +02:00
Jason A. Donenfeld 75efd13556 ui: don't create fake theme color attr
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld cd812b03cd ui: force dark theme in TV mode
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld 794a2a1925 ui: use filled icon buttons in TV mode
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld 75899594c3 ui: use real M3 themeing on TV
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld 4944762d7c ui: redirect to play store if no file manager on tv
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld d6431db248 ui: detect tv framework stubs when getting content
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld b8f649d1cb ui: use ErrorMessages consistently
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 20:53:42 +02:00
Jason A. Donenfeld 9fa8ca8c77 ui: do not crash if tunnel preparation fails
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
Jason A. Donenfeld 59da677c23 ui: do not OOM when leaving log window open for a while
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
Jason A. Donenfeld 18a06b0a51 ui: show bottom sheet sync to prevent double add
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
Jason A. Donenfeld 5e844bed5e ui: do not return to detail page when restoring state
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
Jason A. Donenfeld 46bf98d7f6 ui: never access CREATOR directly
All of the parcelers have their own type prefix. So we have to actually
use the legit methods. This is a bit annoying, as there's no fully
compatible way across all API versions, so we have to branch.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
Jason A. Donenfeld 3e9ddd8720 ui: never throw on link clicks
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-04 15:31:52 +02:00
65 changed files with 587 additions and 359 deletions
+1 -1
View File
@@ -14,5 +14,5 @@ build/
*.dex
*.iml
*.jks
keystore.properties
gradlew.bat
maint/
+3
View File
@@ -1,3 +1,6 @@
[submodule "tunnel/tools/wireguard-tools"]
path = tunnel/tools/wireguard-tools
url = https://git.zx2c4.com/wireguard-tools
[submodule "tunnel/tools/elf-cleaner"]
path = tunnel/tools/elf-cleaner
url = https://github.com/termux/termux-elf-cleaner
+6 -6
View File
@@ -1,20 +1,20 @@
buildscript {
ext {
activityVersion = '1.6.1'
activityVersion = '1.7.1'
annotationsVersion = '1.6.0'
appcompatVersion = '1.6.1'
biometricVersion = '1.1.0'
collectionVersion = '1.2.0'
constraintLayoutVersion = '2.1.4'
coordinatorLayoutVersion = '1.2.0'
coreKtxVersion = '1.10.0-rc01'
coreKtxVersion = '1.10.0'
coroutinesVersion = '1.6.4'
datastoreVersion = '1.0.0'
desugarVersion = '1.1.5'
fragmentVersion = '1.5.5'
fragmentVersion = '1.5.7'
jsr305Version = '3.0.2'
junitVersion = '4.13.2'
lifecycleRuntimeKtxVersion = '2.6.0'
lifecycleRuntimeKtxVersion = '2.6.1'
materialComponentsVersion = '1.8.0'
preferenceVersion = '1.2.0'
zxingEmbeddedVersion = '4.3.0'
@@ -25,8 +25,8 @@ buildscript {
tasks {
wrapper {
gradleVersion = "8.0.2"
distributionSha256Sum = "ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7"
gradleVersion = "8.1.1"
distributionSha256Sum = "e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f"
}
}
+2 -4
View File
@@ -42,8 +42,6 @@ android.generateManifestClass=true
android.experimental.enableSourceSetPathsMap=true
# Use relative paths for better Gradle caching of library build tasks
android.experimental.cacheCompileLibResources=true
# Disable automatic creation of publishing components
android.disableAutomaticComponentCreation=true
# Default Android build features
# Disable BuildConfig generation by default
@@ -61,8 +59,8 @@ android.library.defaults.buildfeatures.androidresources=false
# Suppress warnings for some features that aren't yet stabilized
android.suppressUnsupportedOptionWarnings=android.keepWorkerActionServicesBetweenBuilds,\
android.experimental.enableNewResourceShrinker,android.experimental.enableNewResourceShrinker.preciseShrinking,\
android.enableAppCompileTimeRClass,android.experimental.enableSourceSetPathsMap,android.experimental.cacheCompileLibResources,\
android.experimental.enableNewResourceShrinker.preciseShrinking,\
android.enableAppCompileTimeRClass,\
android.suppressUnsupportedOptionWarnings
# OSSRH sometimes struggles with slow deployments, so this makes Gradle
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+4 -3
View File
@@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
+1 -1
View File
@@ -1,5 +1,5 @@
pluginManagement {
def agpVersion = '7.4.2'
def agpVersion = '8.0.0'
def kotlinVersion = "1.8.0"
repositories {
gradlePluginPortal()
+10 -2
View File
@@ -29,17 +29,25 @@ android {
}
}
buildTypes {
all {
externalNativeBuild {
cmake {
targets 'libwg-go.so', 'libwg.so', 'libwg-quick.so'
arguments "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
release {
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
arguments "-DANDROID_PACKAGE_NAME=${groupName}"
}
}
}
debug {
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug"
}
}
}
@@ -24,6 +24,7 @@ import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
import java.net.InetAddress;
import java.time.Instant;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -125,12 +126,14 @@ public final class GoBackend implements Backend {
Key key = null;
long rx = 0;
long tx = 0;
long latestHandshakeMSec = 0;
for (final String line : config.split("\\n")) {
if (line.startsWith("public_key=")) {
if (key != null)
stats.add(key, rx, tx);
stats.add(key, rx, tx, latestHandshakeMSec);
rx = 0;
tx = 0;
latestHandshakeMSec = 0;
try {
key = Key.fromHex(line.substring(11));
} catch (final KeyFormatException ignored) {
@@ -152,10 +155,26 @@ public final class GoBackend implements Backend {
} catch (final NumberFormatException ignored) {
tx = 0;
}
} else if (line.startsWith("last_handshake_time_sec=")) {
if (key == null)
continue;
try {
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
} catch (final NumberFormatException ignored) {
latestHandshakeMSec = 0;
}
} else if (line.startsWith("last_handshake_time_nsec=")) {
if (key == null)
continue;
try {
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
} catch (final NumberFormatException ignored) {
latestHandshakeMSec = 0;
}
}
}
if (key != null)
stats.add(key, rx, tx);
stats.add(key, rx, tx, latestHandshakeMSec);
return stats;
}
@@ -324,6 +343,9 @@ public final class GoBackend implements Backend {
currentTunnelHandle = -1;
currentConfig = null;
wgTurnOff(handleToClose);
try {
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
} catch (final TimeoutException ignored) { }
}
tunnel.onStateChange(state);
@@ -6,36 +6,65 @@
package com.wireguard.android.backend;
import android.os.SystemClock;
import android.util.Pair;
import com.wireguard.crypto.Key;
import com.wireguard.util.NonNullForAll;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import androidx.annotation.Nullable;
/**
* Class representing transfer statistics for a {@link Tunnel} instance.
*/
@NonNullForAll
public class Statistics {
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
// TODO: switch to Java Record class once R8 supports desugaring those.
public final class PeerStats {
public final long rxBytes, txBytes, latestHandshakeEpochMillis;
PeerStats(final long rxBytes, final long txBytes, final long latestHandshakeEpochMillis) {
this.rxBytes = rxBytes;
this.txBytes = txBytes;
this.latestHandshakeEpochMillis = latestHandshakeEpochMillis;
}
@Override public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final PeerStats stats = (PeerStats) o;
return rxBytes == stats.rxBytes && txBytes == stats.txBytes && latestHandshakeEpochMillis == stats.latestHandshakeEpochMillis;
}
@Override public int hashCode() {
return Objects.hash(rxBytes, txBytes, latestHandshakeEpochMillis);
}
}
private final Map<Key, PeerStats> stats = new HashMap<>();
private long lastTouched = SystemClock.elapsedRealtime();
Statistics() {
}
/**
* Add a peer and its current data usage to the internal map.
* Add a peer and its current stats to the internal map.
*
* @param key A WireGuard public key bound to a particular peer
* @param rx The received traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes
* @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes.
* @param key A WireGuard public key bound to a particular peer
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes.
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
*/
void add(final Key key, final long rx, final long tx) {
peerBytes.put(key, Pair.create(rx, tx));
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
lastTouched = SystemClock.elapsedRealtime();
}
@@ -49,31 +78,14 @@ public class Statistics {
}
/**
* Get the received traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
*
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
* @return a long representing the number of bytes received by this peer.
* @return a {@link PeerStats} representing various statistics about this peer.
*/
public long peerRx(final Key peer) {
final Pair<Long, Long> rxTx = peerBytes.get(peer);
if (rxTx == null)
return 0;
return rxTx.first;
}
/**
* Get the transmitted traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}
*
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
* @return a long representing the number of bytes transmitted by this peer.
*/
public long peerTx(final Key peer) {
final Pair<Long, Long> rxTx = peerBytes.get(peer);
if (rxTx == null)
return 0;
return rxTx.second;
@Nullable
public PeerStats peer(final Key peer) {
return this.stats.get(peer);
}
/**
@@ -83,7 +95,7 @@ public class Statistics {
* {@link com.wireguard.config.Peer}s
*/
public Key[] peers() {
return peerBytes.keySet().toArray(new Key[0]);
return stats.keySet().toArray(new Key[0]);
}
/**
@@ -93,8 +105,8 @@ public class Statistics {
*/
public long totalRx() {
long rx = 0;
for (final Pair<Long, Long> val : peerBytes.values()) {
rx += val.first;
for (final PeerStats val : stats.values()) {
rx += val.rxBytes;
}
return rx;
}
@@ -106,8 +118,8 @@ public class Statistics {
*/
public long totalTx() {
long tx = 0;
for (final Pair<Long, Long> val : peerBytes.values()) {
tx += val.second;
for (final PeerStats val : stats.values()) {
tx += val.txBytes;
}
return tx;
}
@@ -20,6 +20,7 @@ import com.wireguard.util.NonNullForAll;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -83,17 +84,17 @@ public final class WgQuickBackend implements Backend {
final Statistics stats = new Statistics();
final Collection<String> output = new ArrayList<>();
try {
if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
return stats;
} catch (final Exception ignored) {
return stats;
}
for (final String line : output) {
final String[] parts = line.split("\\t");
if (parts.length != 3)
if (parts.length != 8)
continue;
try {
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
} catch (final Exception ignored) {
}
}
+16 -4
View File
@@ -6,15 +6,16 @@ cmake_minimum_required(VERSION 3.4.1)
project("WireGuard")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
add_link_options(LINKER:--build-id=none)
add_compile_options(-Wall -Werror)
add_executable(libwg-quick.so wireguard-tools/src/wg-quick/android.c ndk-compat/compat.c)
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_compile_options(libwg-quick.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_link_libraries(libwg-quick.so -ldl)
file(GLOB WG_SOURCES wireguard-tools/src/*.c ndk-compat/compat.c)
add_executable(libwg.so ${WG_SOURCES})
target_include_directories(libwg.so PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/uapi/linux/" "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/")
target_compile_options(libwg.so PUBLIC -O3 -std=gnu11 -D_GNU_SOURCE -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
target_compile_options(libwg.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND "${ANDROID_HOST_PREBUILTS}/bin/make"
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
@@ -28,5 +29,16 @@ add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
BUILDDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../generated-src
)
# Hack to make it actually build as part of the default target
add_dependencies(libwg.so libwg-go.so)
# Strip unwanted ELF sections to prevent DT_FLAGS_1 warnings on old Android versions
file(GLOB ELF_CLEANER_SOURCES elf-cleaner/*.c elf-cleaner/*.cpp)
add_custom_target(elf-cleaner COMMENT "Building elf-cleaner" VERBATIM COMMAND cc
-O2 -DPACKAGE_NAME="elf-cleaner" -DPACKAGE_VERSION="" -DCOPYRIGHT=""
-o "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner" ${ELF_CLEANER_SOURCES}
)
add_custom_command(TARGET libwg.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg.so>")
add_dependencies(libwg.so elf-cleaner)
add_custom_command(TARGET libwg-quick.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg-quick.so>")
add_dependencies(libwg-quick.so elf-cleaner)
+5 -5
View File
@@ -20,12 +20,12 @@ export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
export GOOS := android
export CGO_ENABLED := 1
GO_VERSION := 1.20.2
GO_VERSION := 1.20.3
GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m))
GO_TARBALL := go$(GO_VERSION).$(GO_PLATFORM).tar.gz
GO_HASH_darwin-amd64 := c93b8ced9517d07e1cd4c362c6e2d5242cb139e29b417a328fbf19aded08764c
GO_HASH_darwin-arm64 := 7343c87f19e79c0063532e82e1c4d6f42175a32d99f7a4d15e658e88bf97f885
GO_HASH_linux-amd64 := 4eaea32f59cde4dc635fbc42161031d13e1c780b87097f4b4234cfce671f1768
GO_HASH_darwin-amd64 := c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565
GO_HASH_darwin-arm64 := 86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95
GO_HASH_linux-amd64 := 979694c2c25c735755bf26f4f45e19e64e4811d661dd07b8c010f7a8e18adfca
default: $(DESTDIR)/libwg-go.so
@@ -47,6 +47,6 @@ $(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_T
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard -buildid=" -v -trimpath -o "$@" -buildmode c-shared
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard -buildid=" -v -trimpath -buildvcs=false -o "$@" -buildmode c-shared
.DELETE_ON_ERROR:
@@ -1,4 +1,4 @@
From 729c58cb1c0496497dac6de3d0bf540f6149618f Mon Sep 17 00:00:00 2001
From 61f3ae8298d1c503cbc31539e0f3a73446c7db9d Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Tue, 21 Mar 2023 15:33:56 +0100
Subject: [PATCH] [release-branch.go1.20] runtime: use CLOCK_BOOTTIME in
@@ -22,11 +22,11 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
src/runtime/sys_linux_amd64.s | 2 +-
src/runtime/sys_linux_arm.s | 4 ++--
src/runtime/sys_linux_arm64.s | 4 ++--
src/runtime/sys_linux_mips64x.s | 2 +-
src/runtime/sys_linux_mips64x.s | 4 ++--
src/runtime/sys_linux_mipsx.s | 2 +-
src/runtime/sys_linux_ppc64x.s | 2 +-
src/runtime/sys_linux_s390x.s | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
8 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
index 12a294153d..17e3524b40 100644
@@ -106,7 +106,7 @@ index 38ff6ac330..6b819c5441 100644
CBZ R2, fallback
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
index 47f2da524d..6c1a9a2801 100644
index 47f2da524d..a8b387f193 100644
--- a/src/runtime/sys_linux_mips64x.s
+++ b/src/runtime/sys_linux_mips64x.s
@@ -326,7 +326,7 @@ noswitch:
@@ -118,6 +118,15 @@ index 47f2da524d..6c1a9a2801 100644
MOVV $0(R29), R5
MOVV runtime·vdsoClockgettimeSym(SB), R25
@@ -336,7 +336,7 @@ noswitch:
// see walltime for detail
BEQ R2, R0, finish
MOVV R0, runtime·vdsoClockgettimeSym(SB)
- MOVW $1, R4 // CLOCK_MONOTONIC
+ MOVW $7, R4 // CLOCK_BOOTTIME
MOVV $0(R29), R5
JMP fallback
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
index 5e6b6c1504..7f5fd2a80e 100644
--- a/src/runtime/sys_linux_mipsx.s
@@ -158,5 +167,5 @@ index 1448670b91..7d2ee3231c 100644
MOVD R15, R7 // Backup stack pointer
--
2.40.0
2.17.1
-52
View File
@@ -6,58 +6,6 @@
#define FILE_IS_EMPTY
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#undef FILE_IS_EMPTY
#include <stdio.h>
#include <stdlib.h>
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
{
char *ptr, *eptr;
if (*buf == NULL || *bufsiz == 0) {
*bufsiz = BUFSIZ;
if ((*buf = malloc(*bufsiz)) == NULL)
return -1;
}
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
int c = fgetc(fp);
if (c == -1) {
if (feof(fp)) {
ssize_t diff = (ssize_t)(ptr - *buf);
if (diff != 0) {
*ptr = '\0';
return diff;
}
}
return -1;
}
*ptr++ = c;
if (c == delimiter) {
*ptr = '\0';
return ptr - *buf;
}
if (ptr + 2 >= eptr) {
char *nbuf;
size_t nbufsiz = *bufsiz * 2;
ssize_t d = ptr - *buf;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
return -1;
*buf = nbuf;
*bufsiz = nbufsiz;
eptr = nbuf + nbufsiz;
ptr = nbuf + d;
}
}
}
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
{
return getdelim(buf, bufsiz, '\n', fp);
}
#endif
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
#undef FILE_IS_EMPTY
#include <string.h>
-6
View File
@@ -4,12 +4,6 @@
*
*/
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#include <stdio.h>
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
#endif
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
char *strchrnul(const char *s, int c);
#endif
+2 -22
View File
@@ -9,10 +9,6 @@ plugins {
version wireguardVersionName
group groupName
// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
final def keystorePropertiesFile = rootProject.file("keystore.properties")
android {
compileSdk 33
buildFeatures {
@@ -34,27 +30,11 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled = true
}
if (keystorePropertiesFile.exists()) {
final def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile rootProject.file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
}
buildTypes {
all {
buildConfigField('boolean', 'IS_GOOGLE_PLAY', (findProperty('build.google_play') == 'true').toString())
}
release {
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
minifyEnabled true
proguardFiles "proguard-android-optimize.txt", "proguard-rules.pro"
shrinkResources true
proguardFiles "proguard-android-optimize.txt"
packagingOptions {
exclude "DebugProbesKt.bin"
exclude "kotlin-tooling-metadata.json"
+1 -2
View File
@@ -1,7 +1,6 @@
-allowaccessmodification
-dontpreverify
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontobfuscate
-verbose
-keepattributes *Annotation*
-14
View File
@@ -1,14 +0,0 @@
# Squelch all warnings, they're harmless but ProGuard
# escalates them as errors.
-dontwarn sun.misc.Unsafe
# Fragment 1.2.4 allows Fragment classes to be obfuscated but
# databinding references in XML seem to not be rewritten to
# match, so we preserve the names as 1.2.3 did.
-if public class ** extends androidx.fragment.app.Fragment
-keep public class <1> {
public <init>();
}
# Don't obfuscate
-dontobfuscate
@@ -17,8 +17,6 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.DynamicColorsOptions
import com.wireguard.android.activity.TvMainActivity
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
@@ -37,6 +35,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.ref.WeakReference
import java.util.Locale
@@ -59,10 +58,6 @@ class Application : android.app.Application() {
startActivity(intent)
System.exit(0)
}
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
}
}
private suspend fun determineBackend(): Backend {
@@ -89,17 +84,24 @@ class Application : android.app.Application() {
override fun onCreate() {
Log.i(TAG, USER_AGENT)
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this,
// TODO: Remove this second argument once the TV theme has a proper M3 color palette.
DynamicColorsOptions.Builder().setPrecondition { activity, _ -> activity !is TvMainActivity }.build())
DynamicColors.applyToActivitiesIfAvailable(this)
rootShell = RootShell(applicationContext)
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
coroutineScope.launch {
AppCompatDelegate.setDefaultNightMode(
if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
runBlocking {
AppCompatDelegate.setDefaultNightMode(if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
}
UserKnobs.darkTheme.onEach {
val newMode = if (it) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
AppCompatDelegate.setDefaultNightMode(newMode)
}
}.launchIn(coroutineScope)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
@@ -113,6 +115,11 @@ class Application : android.app.Application() {
Log.e(TAG, Log.getStackTraceString(e))
}
}
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
}
}
override fun onTerminate() {
@@ -5,6 +5,7 @@
package com.wireguard.android.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.CallbackRegistry
import androidx.databinding.CallbackRegistry.NotifierCallback
import androidx.lifecycle.lifecycleScope
@@ -15,15 +16,21 @@ import kotlinx.coroutines.launch
/**
* Base class for activities that need to remember the currently-selected tunnel.
*/
abstract class BaseActivity : ThemeChangeAwareActivity() {
abstract class BaseActivity : AppCompatActivity() {
private val selectionChangeRegistry = SelectionChangeRegistry()
private var created = false
var selectedTunnel: ObservableTunnel? = null
set(value) {
val oldTunnel = field
if (oldTunnel == value) return
field = value
onSelectedTunnelChanged(oldTunnel, value)
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, value)
if (created) {
if (!onSelectedTunnelChanged(oldTunnel, value)) {
field = oldTunnel
} else {
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, value)
}
}
}
fun addOnSelectedTunnelChangedListener(listener: OnSelectedTunnelChangedListener) {
@@ -39,10 +46,17 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
intent != null -> intent.getStringExtra(KEY_SELECTED_TUNNEL)
else -> null
}
if (savedTunnelName != null)
if (savedTunnelName != null) {
lifecycleScope.launch {
selectedTunnel = Application.getTunnelManager().getTunnels()[savedTunnelName]
val tunnel = Application.getTunnelManager().getTunnels()[savedTunnelName]
if (tunnel == null)
created = true
selectedTunnel = tunnel
created = true
}
} else {
created = true
}
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -50,7 +64,7 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
super.onSaveInstanceState(outState)
}
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?)
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean
fun removeOnSelectedTunnelChangedListener(
listener: OnSelectedTunnelChangedListener) {
@@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.CircularArray
import androidx.core.app.ShareCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.lifecycleScope
@@ -62,8 +63,8 @@ import java.util.regex.Pattern
class LogViewerActivity : AppCompatActivity() {
private lateinit var binding: LogViewerActivityBinding
private lateinit var logAdapter: LogEntryAdapter
private var logLines = arrayListOf<LogLine>()
private var rawLogLines = StringBuffer()
private var logLines = CircularArray<LogLine>()
private var rawLogLines = CircularArray<String>()
private var recyclerView: RecyclerView? = null
private var saveButton: MenuItem? = null
private val year by lazy {
@@ -113,7 +114,7 @@ class LogViewerActivity : AppCompatActivity() {
binding.shareFab.setOnClickListener {
revokeLastUri()
val key = KeyPair().privateKey.toHex()
LOGS[key] = rawLogLines.toString().toByteArray(Charsets.UTF_8)
LOGS[key] = rawLogBytes()
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
val shareIntent = ShareCompat.IntentBuilder(this)
.setType("text/plain")
@@ -150,13 +151,22 @@ class LogViewerActivity : AppCompatActivity() {
private val downloadsFileSaver = DownloadsFileSaver(this)
private fun rawLogBytes() : ByteArray {
val builder = StringBuilder()
for (i in 0 until rawLogLines.size()) {
builder.append(rawLogLines[i])
builder.append('\n')
}
return builder.toString().toByteArray(Charsets.UTF_8)
}
private suspend fun saveLog() {
var exception: Throwable? = null
var outputFile: DownloadsFileSaver.DownloadsFile? = null
withContext(Dispatchers.IO) {
try {
outputFile = downloadsFileSaver.save("wireguard-log.txt", "text/plain", true)
outputFile?.outputStream?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
outputFile?.outputStream?.write(rawLogBytes())
} catch (e: Throwable) {
outputFile?.delete()
exception = e
@@ -191,24 +201,27 @@ class LogViewerActivity : AppCompatActivity() {
var priorModified = false
val bufferedLogLines = arrayListOf<LogLine>()
var timeout = 1000000000L / 2 // The timeout is initially small so that the view gets populated immediately.
val MAX_LINES = (1 shl 16) - 1
val MAX_BUFFERED_LINES = (1 shl 14) - 1
while (true) {
val line = stdout.readLine() ?: break
rawLogLines.append(line)
rawLogLines.append('\n')
if (rawLogLines.size() >= MAX_LINES)
rawLogLines.popFirst()
rawLogLines.addLast(line)
val logLine = parseLine(line)
if (logLine != null) {
bufferedLogLines.add(logLine)
} else {
if (bufferedLogLines.isNotEmpty()) {
bufferedLogLines.last().msg += "\n$line"
} else if (logLines.isNotEmpty()) {
logLines.last().msg += "\n$line"
} else if (!logLines.isEmpty) {
logLines[logLines.size() - 1].msg += "\n$line"
priorModified = true
}
}
val timeNow = System.nanoTime()
if ((timeNow - timeLastNotify) < timeout && stdout.ready())
if (bufferedLogLines.size < MAX_BUFFERED_LINES && (timeNow - timeLastNotify) < timeout && stdout.ready())
continue
timeout = 1000000000L * 5 / 2 // Increase the timeout after the initial view has something in it.
timeLastNotify = timeNow
@@ -219,13 +232,23 @@ class LogViewerActivity : AppCompatActivity() {
logAdapter.notifyItemChanged(posStart - 1)
priorModified = false
}
logLines.addAll(bufferedLogLines)
val fullLen = logLines.size() + bufferedLogLines.size
if (fullLen >= MAX_LINES) {
val numToRemove = fullLen - MAX_LINES + 1
logLines.removeFromStart(numToRemove)
logAdapter.notifyItemRangeRemoved(0, numToRemove)
posStart -= numToRemove
}
for (bufferedLine in bufferedLogLines) {
logLines.addLast(bufferedLine)
}
bufferedLogLines.clear()
logAdapter.notifyItemRangeInserted(posStart, logLines.size - posStart)
posStart = logLines.size
logAdapter.notifyItemRangeInserted(posStart, logLines.size() - posStart)
posStart = logLines.size()
if (isScrolledToBottomAlready) {
recyclerView?.scrollToPosition(logLines.size - 1)
recyclerView?.scrollToPosition(logLines.size() - 1)
}
}
}
@@ -279,7 +302,7 @@ class LogViewerActivity : AppCompatActivity() {
}
}
override fun getItemCount() = logLines.size
override fun getItemCount() = logLines.size()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
@@ -96,13 +96,17 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
}
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
newTunnel: ObservableTunnel?) {
newTunnel: ObservableTunnel?): Boolean {
val fragmentManager = supportFragmentManager
if (fragmentManager.isStateSaved) {
return false
}
val backStackEntries = fragmentManager.backStackEntryCount
if (newTunnel == null) {
// Clear everything off the back stack (all editors and detail fragments).
fragmentManager.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE)
return
return true
}
if (backStackEntries == 2) {
// Pop the editor off the back stack to reveal the detail fragment. Use the immediate
@@ -116,5 +120,6 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
addToBackStack(null)
}
}
return true
}
}
@@ -8,6 +8,7 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
@@ -24,7 +25,7 @@ import kotlinx.coroutines.withContext
/**
* Interface for changing application-global persistent settings.
*/
class SettingsActivity : ThemeChangeAwareActivity() {
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
@@ -1,33 +0,0 @@
/*
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope
import com.wireguard.android.util.UserKnobs
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
abstract class ThemeChangeAwareActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
UserKnobs.darkTheme.onEach {
val newMode = if (it) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
AppCompatDelegate.setDefaultNightMode(newMode)
recreate()
}
}.launchIn(lifecycleScope)
}
}
}
@@ -22,7 +22,8 @@ class TunnelCreatorActivity : BaseActivity() {
}
}
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean {
finish()
return true
}
}
@@ -6,6 +6,9 @@
package com.wireguard.android.activity
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
@@ -19,6 +22,7 @@ import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.forEach
@@ -29,6 +33,7 @@ import androidx.databinding.ObservableField
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.backend.GoBackend
@@ -43,6 +48,8 @@ import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.QuantityFormatter
import com.wireguard.android.util.TunnelImporter
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.applicationScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -50,7 +57,27 @@ import kotlinx.coroutines.withContext
import java.io.File
class TvMainActivity : AppCompatActivity() {
private val tunnelFileImportResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { data ->
private val tunnelFileImportResultLauncher = registerForActivityResult(object : ActivityResultContracts.GetContent() {
override fun createIntent(context: Context, input: String): Intent {
val intent = super.createIntent(context, input)
/* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
* what we can do, so detect this and throw an exception that we can catch later. */
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) { data ->
if (data == null) return@registerForActivityResult
lifecycleScope.launch {
TunnelImporter.importTunnel(contentResolver, data) {
@@ -86,6 +113,14 @@ class TvMainActivity : AppCompatActivity() {
private val filesRoot = ObservableField("")
override fun onCreate(savedInstanceState: Bundle?) {
if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
applicationScope.launch {
UserKnobs.setDarkTheme(true)
}
}
}
super.onCreate(savedInstanceState)
binding = TvActivityBinding.inflate(layoutInflater)
lifecycleScope.launch {
@@ -176,7 +211,12 @@ class TvMainActivity : AppCompatActivity() {
try {
tunnelFileImportResultLauncher.launch("*/*")
} catch (_: Throwable) {
Toast.makeText(this@TvMainActivity, getString(R.string.tv_no_file_picker), Toast.LENGTH_LONG).show()
MaterialAlertDialogBuilder(binding.root.context).setMessage(R.string.tv_no_file_picker).setCancelable(false).setPositiveButton(android.R.string.ok) { _, _ ->
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
} catch (_: Throwable) {
}
}.show()
}
}
}
@@ -23,6 +23,7 @@ import com.wireguard.android.R
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
import com.wireguard.android.widget.ToggleSwitch
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener
import com.wireguard.android.widget.TvCardView
import com.wireguard.config.Attribute
import com.wireguard.config.InetNetwork
import java.net.InetAddress
@@ -168,4 +169,16 @@ object BindingAdapters {
0
}
}
@JvmStatic
@BindingAdapter("isUp")
fun setIsUp(card: TvCardView, up: Boolean) {
card.isUp = up
}
@JvmStatic
@BindingAdapter("isDeleting")
fun setIsDeleting(card: TvCardView, deleting: Boolean) {
card.isDeleting = deleting
}
}
@@ -63,8 +63,8 @@ class AppListDialogFragment : DialogFragment() {
withContext(Dispatchers.Main.immediate) {
appData.clear()
appData.addAll(applicationData)
setButtonText()
}
setButtonText()
} catch (e: Throwable) {
withContext(Dispatchers.Main.immediate) {
val error = ErrorMessages[e]
@@ -67,12 +67,20 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
val activity = activity ?: return
activity.lifecycleScope.launch {
if (Application.getBackend() is GoBackend) {
val intent = GoBackend.VpnService.prepare(activity)
if (intent != null) {
pendingTunnel = tunnel
pendingTunnelUp = checked
permissionActivityResultLauncher.launch(intent)
return@launch
try {
val intent = GoBackend.VpnService.prepare(activity)
if (intent != null) {
pendingTunnel = tunnel
pendingTunnelUp = checked
permissionActivityResultLauncher.launch(intent)
return@launch
}
} catch (e: Throwable) {
val message = activity.getString(R.string.error_prepare, ErrorMessages[e])
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
.setAnchorView(view.findViewById(R.id.create_fab))
.show()
Log.e(TAG, message, e)
}
}
setTunnelStateWithPermissionsResult(tunnel, checked)
@@ -112,16 +112,25 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
?: continue
val publicKey = peer.item!!.publicKey
val rx = statistics.peerRx(publicKey)
val tx = statistics.peerTx(publicKey)
if (rx == 0L && tx == 0L) {
val peerStats = statistics.peer(publicKey)
if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) {
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
continue
} else {
peer.transferText.text = getString(R.string.transfer_rx_tx,
QuantityFormatter.formatBytes(peerStats.rxBytes),
QuantityFormatter.formatBytes(peerStats.txBytes))
peer.transferLabel.visibility = View.VISIBLE
peer.transferText.visibility = View.VISIBLE
}
if (peerStats == null || peerStats.latestHandshakeEpochMillis == 0L) {
peer.latestHandshakeLabel.visibility = View.GONE
peer.latestHandshakeText.visibility = View.GONE
} else {
peer.latestHandshakeText.text = QuantityFormatter.formatEpochAgo(peerStats.latestHandshakeEpochMillis)
peer.latestHandshakeLabel.visibility = View.VISIBLE
peer.latestHandshakeText.visibility = View.VISIBLE
}
peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
peer.transferLabel.visibility = View.VISIBLE
peer.transferText.visibility = View.VISIBLE
}
} catch (e: Throwable) {
for (i in 0 until binding.peersLayout.childCount) {
@@ -129,6 +138,8 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
?: continue
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
peer.latestHandshakeLabel.visibility = View.GONE
peer.latestHandshakeText.visibility = View.GONE
}
}
}
@@ -115,7 +115,7 @@ class TunnelListFragment : BaseFragment() {
}
}
}
bottomSheet.show(childFragmentManager, "BOTTOM_SHEET")
bottomSheet.showNow(childFragmentManager, "BOTTOM_SHEET")
}
executePendingBindings()
}
@@ -5,15 +5,16 @@
package com.wireguard.android.preference
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.AttributeSet
import android.widget.Toast
import androidx.preference.Preference
import com.wireguard.android.BuildConfig
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.wireguard.android.R
import com.wireguard.android.util.ErrorMessages
class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
override fun getSummary() = context.getString(R.string.donate_summary)
@@ -21,18 +22,34 @@ class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(cont
override fun getTitle() = context.getString(R.string.donate_title)
override fun onClick() {
if (BuildConfig.IS_GOOGLE_PLAY) {
AlertDialog.Builder(context)
val installer = try {
val packageName = context.packageName
val pm = context.packageManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
} else {
@Suppress("DEPRECATION")
pm.getInstallerPackageName(packageName) ?: ""
}
} catch (_: Throwable) {
""
}
/* Google Play Store forbids links to our donation page. */
if (installer == "com.android.vending") {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.donate_title)
.setMessage(R.string.donate_google_play_disappointment)
.show()
return
}
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.wireguard.com/donations/")
try {
context.startActivity(intent)
} catch (ignored: ActivityNotFoundException) {
} catch (e: Throwable) {
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
}
}
}
}
@@ -4,11 +4,11 @@
*/
package com.wireguard.android.preference
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.AttributeSet
import android.widget.Toast
import androidx.preference.Preference
import com.wireguard.android.Application
import com.wireguard.android.BuildConfig
@@ -16,6 +16,7 @@ import com.wireguard.android.R
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -33,7 +34,8 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
intent.data = Uri.parse("https://www.wireguard.com/")
try {
context.startActivity(intent)
} catch (_: ActivityNotFoundException) {
} catch (e: Throwable) {
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
}
}
@@ -92,8 +92,8 @@ object ErrorMessages {
rootCause is ChecksumException -> {
resources.getString(R.string.error_qr_checksum)
}
rootCause.message != null -> {
rootCause.message!!
rootCause.localizedMessage != null -> {
rootCause.localizedMessage!!
}
else -> {
val errorType = rootCause.javaClass.simpleName
@@ -109,7 +109,7 @@ object ErrorMessages {
if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format))
} else if (bce.cause is ParseException) {
val pe = bce.cause as ParseException?
if (pe!!.message != null) return ": ${pe.message}"
if (pe!!.localizedMessage != null) return ": ${pe.localizedMessage}"
} else if (bce.location == BadConfigException.Location.LISTEN_PORT) {
return resources.getString(R.string.bad_config_explanation_udp_port)
} else if (bce.location == BadConfigException.Location.MTU) {
@@ -5,8 +5,17 @@
package com.wireguard.android.util
import android.icu.text.ListFormatter
import android.icu.text.MeasureFormat
import android.icu.text.RelativeDateTimeFormatter
import android.icu.util.Measure
import android.icu.util.MeasureUnit
import android.os.Build
import com.wireguard.android.Application
import com.wireguard.android.R
import java.util.Locale
import kotlin.time.DurationUnit
import kotlin.time.toDuration
object QuantityFormatter {
fun formatBytes(bytes: Long): String {
@@ -19,4 +28,40 @@ object QuantityFormatter {
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
}
}
fun formatEpochAgo(epochMillis: Long): String {
var span = (System.currentTimeMillis() - epochMillis) / 1000
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, span.toDuration(DurationUnit.SECONDS).toString())
if (span <= 0L)
return RelativeDateTimeFormatter.getInstance().format(RelativeDateTimeFormatter.Direction.PLAIN, RelativeDateTimeFormatter.AbsoluteUnit.NOW)
val measureFormat = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
val parts = ArrayList<CharSequence>(4)
if (span >= 24 * 60 * 60L) {
val v = span / (24 * 60 * 60L)
parts.add(measureFormat.format(Measure(v, MeasureUnit.DAY)))
span -= v * (24 * 60 * 60L)
}
if (span >= 60 * 60L) {
val v = span / (60 * 60L)
parts.add(measureFormat.format(Measure(v, MeasureUnit.HOUR)))
span -= v * (60 * 60L)
}
if (span >= 60L) {
val v = span / 60L
parts.add(measureFormat.format(Measure(v, MeasureUnit.MINUTE)))
span -= v * 60L
}
if (span > 0L)
parts.add(measureFormat.format(Measure(span, MeasureUnit.SECOND)))
val joined = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
parts.joinToString()
else
ListFormatter.getInstance(Locale.getDefault(), ListFormatter.Type.UNITS, ListFormatter.Width.SHORT).format(parts)
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, joined)
}
}
@@ -41,6 +41,12 @@ object UserKnobs {
it[DARK_THEME] ?: false
}
suspend fun setDarkTheme(on: Boolean) {
Application.getPreferencesDataStore().edit {
it[DARK_THEME] = on
}
}
private val ALLOW_REMOTE_CONTROL_INTENTS = booleanPreferencesKey("allow_remote_control_intents")
val allowRemoteControlIntents: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
@@ -4,22 +4,27 @@
*/
package com.wireguard.android.viewmodel
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ParcelCompat
import androidx.databinding.ObservableArrayList
import androidx.databinding.ObservableList
import com.wireguard.config.BadConfigException
import com.wireguard.config.Config
import com.wireguard.config.Peer
import java.util.ArrayList
class ConfigProxy : Parcelable {
val `interface`: InterfaceProxy
val peers: ObservableList<PeerProxy> = ObservableArrayList()
private constructor(parcel: Parcel) {
`interface` = InterfaceProxy.CREATOR.createFromParcel(parcel)
parcel.readTypedList(peers, PeerProxy.CREATOR)
`interface` = ParcelCompat.readParcelable(parcel, InterfaceProxy::class.java.classLoader, InterfaceProxy::class.java) ?: InterfaceProxy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ParcelCompat.readParcelableList(parcel, peers, PeerProxy::class.java.classLoader, PeerProxy::class.java)
} else {
parcel.readTypedList(peers, PeerProxy.CREATOR)
}
peers.forEach { it.bind(this) }
}
@@ -57,7 +62,11 @@ class ConfigProxy : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(`interface`, flags)
dest.writeTypedList(peers)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
dest.writeParcelableList(peers, flags)
} else {
dest.writeTypedList(peers)
}
}
private class ConfigProxyCreator : Parcelable.Creator<ConfigProxy> {
@@ -0,0 +1,44 @@
/*
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
import android.content.Context
import android.util.AttributeSet
import android.view.View
import com.google.android.material.card.MaterialCardView
import com.wireguard.android.R
class TvCardView(context: Context?, attrs: AttributeSet?) : MaterialCardView(context, attrs) {
var isUp: Boolean = false
set(value) {
field = value
refreshDrawableState()
}
var isDeleting: Boolean = false
set(value) {
field = value
refreshDrawableState()
}
override fun onCreateDrawableState(extraSpace: Int): IntArray {
if (isUp || isDeleting) {
val drawableState = super.onCreateDrawableState(extraSpace + (if (isUp) 1 else 0) + (if (isDeleting) 1 else 0))
if (isUp) {
View.mergeDrawableStates(drawableState, STATE_IS_UP)
}
if (isDeleting) {
View.mergeDrawableStates(drawableState, STATE_IS_DELETING)
}
return drawableState
}
return super.onCreateDrawableState(extraSpace)
}
companion object {
private val STATE_IS_UP = intArrayOf(R.attr.state_isUp)
private val STATE_IS_DELETING = intArrayOf(R.attr.state_isDeleting)
}
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item app:state_isUp="true" app:state_isDeleting="false" android:color="?attr/colorPrimaryInverse" />
<item android:state_focused="true" app:state_isDeleting="true" android:color="?attr/colorErrorContainer" />
<item android:color="?attr/colorOnSurfaceInverse" />
</selector>
@@ -6,7 +6,7 @@
<item
android:state_activated="true"
app:state_multiselected="true">
<color android:color="?attr/colorMultiselectActiveBackground" />
<color android:color="?attr/colorSurfaceVariant" />
</item>
<item
android:state_activated="true">
@@ -71,7 +71,7 @@
<TextView
android:id="@+id/interface_name_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/interface_name_text"
@@ -81,7 +81,7 @@
<TextView
android:id="@+id/interface_name_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/name"
android:nextFocusUp="@id/tunnel_switch"
@@ -96,7 +96,7 @@
<TextView
android:id="@+id/public_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/public_key_text"
@@ -106,7 +106,7 @@
<TextView
android:id="@+id/public_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/public_key"
android:ellipsize="end"
@@ -124,7 +124,7 @@
<TextView
android:id="@+id/addresses_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/addresses_text"
@@ -135,7 +135,7 @@
<TextView
android:id="@+id/addresses_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/addresses"
android:nextFocusUp="@id/public_key_text"
@@ -151,7 +151,7 @@
<TextView
android:id="@+id/dns_servers_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_servers_text"
@@ -162,7 +162,7 @@
<TextView
android:id="@+id/dns_servers_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_servers"
android:nextFocusUp="@id/addresses_text"
@@ -178,7 +178,7 @@
<TextView
android:id="@+id/dns_search_domains_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_search_domain_text"
@@ -189,7 +189,7 @@
<TextView
android:id="@+id/dns_search_domains_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_search_domains"
android:nextFocusUp="@id/dns_servers_text"
@@ -277,7 +277,7 @@
<TextView
android:id="@+id/applications_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/applications_text"
@@ -288,7 +288,7 @@
<TextView
android:id="@+id/applications_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/applications"
android:nextFocusUp="@id/mtu_text"
+42 -14
View File
@@ -23,7 +23,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/peer_title"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/peer"
android:textAppearance="?attr/textAppearanceTitleMedium"
@@ -32,7 +32,7 @@
<TextView
android:id="@+id/public_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/public_key_text"
@@ -42,7 +42,7 @@
<TextView
android:id="@+id/public_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/public_key"
android:ellipsize="end"
@@ -59,7 +59,7 @@
<TextView
android:id="@+id/pre_shared_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/pre_shared_key_text"
@@ -70,7 +70,7 @@
<TextView
android:id="@+id/pre_shared_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/pre_shared_key"
android:ellipsize="end"
@@ -88,7 +88,7 @@
<TextView
android:id="@+id/allowed_ips_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/allowed_ips_text"
@@ -99,7 +99,7 @@
<TextView
android:id="@+id/allowed_ips_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/allowed_ips"
android:nextFocusUp="@id/pre_shared_key_text"
@@ -115,7 +115,7 @@
<TextView
android:id="@+id/endpoint_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/endpoint_text"
@@ -126,7 +126,7 @@
<TextView
android:id="@+id/endpoint_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/endpoint"
android:nextFocusUp="@id/allowed_ips_text"
@@ -142,7 +142,7 @@
<TextView
android:id="@+id/persistent_keepalive_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/persistent_keepalive_text"
@@ -153,7 +153,7 @@
<TextView
android:id="@+id/persistent_keepalive_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/persistent_keepalive"
android:nextFocusUp="@id/endpoint_text"
@@ -169,9 +169,9 @@
<TextView
android:id="@+id/transfer_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/endpoint_text"
android:layout_below="@+id/persistent_keepalive_text"
android:layout_marginTop="8dp"
android:labelFor="@+id/transfer_text"
android:text="@string/transfer"
@@ -182,7 +182,7 @@
<TextView
android:id="@+id/transfer_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/transfer_label"
android:contentDescription="@string/transfer"
@@ -194,6 +194,34 @@
app:layout_constraintTop_toBottomOf="@+id/transfer_label"
tools:text="1024 MB"
tools:visibility="visible" />
<TextView
android:id="@+id/latest_handshake_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/transfer_text"
android:layout_marginTop="8dp"
android:labelFor="@+id/latest_handshake_text"
android:text="@string/latest_handshake"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/transfer_text"
tools:visibility="visible" />
<TextView
android:id="@+id/latest_handshake_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/latest_handshake_label"
android:contentDescription="@string/latest_handshake"
android:nextFocusUp="@id/transfer_text"
android:onClick="@{ClipboardUtils::copyTextView}"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/latest_handshake_label"
tools:text="4 minutes, 27 seconds ago"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
+2 -4
View File
@@ -127,7 +127,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/import_button"
style="@style/Widget.Material3.Button.IconButton"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
@@ -135,13 +135,12 @@
android:visibility="@{isDeleting ? View.GONE : View.VISIBLE}"
app:icon="@{filesRoot.isEmpty ? @drawable/ic_action_add_white : @drawable/ic_arrow_back}"
app:iconPadding="0dp"
app:iconTint="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/delete_button"
style="@style/Widget.Material3.Button.IconButton"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
@@ -149,7 +148,6 @@
android:visibility="@{((tunnels.isEmpty &amp;&amp; !isDeleting) || !filesRoot.isEmpty) ? View.GONE : View.VISIBLE}"
app:icon="@{isDeleting ? @drawable/ic_arrow_back : @drawable/ic_action_delete}"
app:iconPadding="0dp"
app:iconTint="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -21,7 +21,6 @@
android:layout_margin="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="0dp"
android:backgroundTint="@color/tv_card_background"
android:checkable="true"
android:focusable="true"
app:contentPadding="8dp">
@@ -35,7 +34,6 @@
android:layout_height="wrap_content"
android:text="@{key}"
android:textAppearance="?attr/textAppearanceTitleLarge"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -28,16 +28,18 @@
type="com.wireguard.android.model.ObservableTunnel" />
</data>
<com.google.android.material.card.MaterialCardView
<com.wireguard.android.widget.TvCardView
android:layout_width="225dp"
android:layout_height="110dp"
android:layout_margin="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="0dp"
android:backgroundTint="@color/tv_list_item_tint"
android:checkable="true"
android:focusable="true"
app:contentPadding="8dp"
android:backgroundTint="@{(item.state == State.UP &amp;&amp; !isDeleting) ? @color/tv_secondary_dark_color : (isDeleting &amp;&amp; isFocused) ? @color/tv_card_delete_background : @color/tv_card_background}">
app:isDeleting="@{isDeleting}"
app:isUp="@{item.state == State.UP}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -49,7 +51,6 @@
android:layout_height="wrap_content"
android:text="@{item.name}"
android:textAppearance="?attr/textAppearanceTitleLarge"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/interface_names.json/names/names/name" />
@@ -76,6 +77,6 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</com.wireguard.android.widget.TvCardView>
</layout>
@@ -111,6 +111,8 @@
<string name="tv_select_a_storage_drive">تحديد محرك تخزين</string>
<string name="tv_no_file_picker">الرجاء تثبيت مدير ملفات لتتمكن من تصفح الملفات</string>
<string name="tv_add_tunnel_get_started">أضف نفقاً لتبدأ</string>
<string name="donate_title">♥️ تبرع لمشروع وايرجارد</string>
<string name="donate_summary">كل مساهمة تساعد</string>
<string name="disable_config_export_title">تعطيل تصدير التكوين</string>
<string name="disable_config_export_description">تعطيل تصدير الإعدادات يجعل المفاتيح الخاصة غير متاحة</string>
<string name="dns_servers">خوادم DNS</string>
@@ -120,6 +122,7 @@
<string name="error_down">خطأ في فصل النفق:%s</string>
<string name="error_fetching_apps">خطأ في جلب قائمة التطبيقات: %s</string>
<string name="error_root">الرجاء الحصول على صلاحيات الروت وحاول مرة أخرى</string>
<string name="error_prepare">خطأ في تحضير النفق: %s</string>
<string name="error_up">خطأ خلال إنشاء النفق: %s</string>
<string name="exclude_private_ips">استبعاد عناوين بروتوكول الإنترنت (IP) الخاصة</string>
<string name="generate_new_private_key">توليد مفتاح خاص جديد</string>
@@ -215,6 +218,7 @@
<string name="tunnel_create_success">تم إنشاء نفق \"%s\" بنجاح</string>
<string name="tunnel_error_already_exists">النفق \"%s\" موجود بالفعل</string>
<string name="tunnel_error_invalid_name">إسم غير صالح</string>
<string name="tunnel_list_placeholder">أضف نفق باستخدام الزر أدناه</string>
<string name="tunnel_name">إسم النفق</string>
<string name="tunnel_on_error">غير قادر على تشغيل النفق (wgTurnOn أعاد %d)</string>
<string name="tunnel_dns_failure">غير قادر على حل اسم مضيف نظام أسماء النطاقات (DNS hostname): \"%s\"</string>
@@ -72,6 +72,8 @@
<string name="config_file_exists_error">Konfigurationsfilen \"%s\" findes allerede</string>
<string name="config_not_found_error">Konfigurationsfilen \"%s\" blev ikke fundet</string>
<string name="config_rename_error">Kan ikke omdøbe konfigurationsfilen \"%s\"</string>
<string name="config_save_error">Kan ikke gemme konfigurationen for \"%1$s\": %2$s</string>
<string name="config_save_success">Konfiguration for \"%s\" blev gemt</string>
<string name="create_activity_title">Opret WireGuard tunnel</string>
<string name="create_empty">Opret fra ny</string>
<string name="create_from_file">Importér fra fil eller arkiv</string>
@@ -88,6 +90,7 @@
<string name="dns_search_domains">DNS-søgedomæner</string>
<string name="edit">Redigér</string>
<string name="endpoint">Slutpunkt</string>
<string name="error_prepare">Fejl ved forberedelse af tunnel: %s</string>
<string name="error_up">Fejl under aktivering af tunnel: %s</string>
<string name="exclude_private_ips">Eksludér private IP-adresser</string>
<string name="generate_new_private_key">Generér ny privat nøgle</string>
@@ -152,6 +155,7 @@
<string name="tunnel_create_success">Tunnelen blev succesfuldt oprettet \"%s\"</string>
<string name="tunnel_error_already_exists">Tunnel \"%s\" eksisterer allerede</string>
<string name="tunnel_error_invalid_name">Ugyldigt navn</string>
<string name="tunnel_list_placeholder">Tilføj en tunnel ved hjælp af knappen nedenfor</string>
<string name="tunnel_name">Tunnel Navn</string>
<string name="tunnel_dns_failure">Kunne ikke opslå DNS adresse: \"%s\"</string>
<string name="tunnel_rename_error">Kan ikke omdøbe tunnel: %s</string>
+3
View File
@@ -107,6 +107,8 @@
<string name="tv_select_a_storage_drive">Sélectionner un disque de stockage</string>
<string name="tv_no_file_picker">Veuillez installer un utilitaire de gestion de fichiers pour parcourir les fichiers</string>
<string name="tv_add_tunnel_get_started">Ajouter un tunnel pour commencer</string>
<string name="donate_title">♥ Faire un don au projet WireGuard</string>
<string name="donate_summary">Chaque contribution aide</string>
<string name="disable_config_export_title">Désactiver l\'export de configuration</string>
<string name="disable_config_export_description">La désactivation de l\'export de configuration rend les clés privées moins accessibles</string>
<string name="dns_servers">Serveurs DNS</string>
@@ -213,6 +215,7 @@
<string name="tunnel_create_success">Tunnel «%s » créé avec succès</string>
<string name="tunnel_error_already_exists">Le tunnel « %s » existe déjà</string>
<string name="tunnel_error_invalid_name">Nom invalide</string>
<string name="tunnel_list_placeholder">Ajoutez un tunnel en utilisant le bouton ci-dessous</string>
<string name="tunnel_name">Nom du tunnel</string>
<string name="tunnel_on_error">Impossible d\'activer le tunnel (wgTurnOn a retourné %d)</string>
<string name="tunnel_dns_failure">Impossible de résoudre le nom d\'hôte DNS: “%s”</string>
-1
View File
@@ -27,6 +27,5 @@
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_inversePrimary</item>
<item name="colorMultiselectActiveBackground">@color/md_theme_dark_surfaceVariant</item>
</style>
</resources>
+11 -1
View File
@@ -21,7 +21,7 @@
<item quantity="other">Importerte %d tunneler</item>
</plurals>
<plurals name="set_excluded_applications">
<item quantity="one">%d ekskludert program</item>
<item quantity="one">%d ekskludert app</item>
<item quantity="other">%d ekskluderte apper</item>
</plurals>
<plurals name="set_included_applications">
@@ -79,6 +79,8 @@
<string name="bad_config_reason_unknown_section">Ukjent seksjon</string>
<string name="bad_config_reason_value_out_of_range">Verdien er utenfor gyldig område</string>
<string name="bad_extension_error">Filen må være .conf eller .zip</string>
<string name="error_no_qr_found">Ingen QR-kode funnet i bildet</string>
<string name="error_qr_checksum">Feil ved sjekksumverifisering av QR-kode</string>
<string name="cancel">Avbryt</string>
<string name="config_delete_error">Kan ikke slette konfigurasjonsfilen %s</string>
<string name="config_exists_error">Konfigurasjon for «%s» finnes allerede</string>
@@ -105,14 +107,19 @@
<string name="tv_select_a_storage_drive">Velg en lagringsenhet</string>
<string name="tv_no_file_picker">Vennligst installer et filhåndteringsverktøy for å bla i filer</string>
<string name="tv_add_tunnel_get_started">Opprett en ny tunnel for å komme i gang</string>
<string name="donate_title">♥ Donér til WireGuard-prosjektet</string>
<string name="donate_summary">Hvert bidrag hjelper</string>
<string name="donate_google_play_disappointment">Takk for at du støtter WireGuard-prosjektet!\n\nPå grunn av Googles retningslinjer, kan vi dessverre ikke linke til den delen av prosjektets nettside der du kan donere. Forhåpentligvis klarer du å finne denne selv!\n\nVi takker igjen for ditt bidrag.</string>
<string name="disable_config_export_title">Deaktiver eksport av konfigurasjon</string>
<string name="disable_config_export_description">Deaktivering av konfigurasjonseksport gjør private nøkler mindre tilgjengelig</string>
<string name="dns_servers">DNS tjenere</string>
<string name="dns_search_domains">Søk gjennom domener</string>
<string name="edit">Rediger</string>
<string name="endpoint">Endepunkt</string>
<string name="error_down">Feil når tunnel skulle tas ned: %s</string>
<string name="error_fetching_apps">Feil ved henting av applikasjonsliste: %s</string>
<string name="error_root">Vennligst få root-tilgang og prøv igjen</string>
<string name="error_prepare">Feil ved klargjøring av tunnel: %s</string>
<string name="error_up">Feil når tunnel skulle tas opp: %s</string>
<string name="exclude_private_ips">Utelukk private IP-adresser</string>
<string name="generate_new_private_key">Lag ny privat nøkkel</string>
@@ -210,13 +217,16 @@
<string name="tunnel_create_success">Opprettet tunnelen «%s»</string>
<string name="tunnel_error_already_exists">Tunnel «%s» finnes allerede</string>
<string name="tunnel_error_invalid_name">Ugyldig navn</string>
<string name="tunnel_list_placeholder">Legg til en tunnel ved å bruke knappen under</string>
<string name="tunnel_name">Tunnelnavn</string>
<string name="tunnel_on_error">Kan ikke slå på tunnel (wgTurnOn returnerte %d)</string>
<string name="tunnel_dns_failure">Kan ikke slå opp DNS-vertsnavn: “%s\"</string>
<string name="tunnel_rename_error">Kan ikke endre navn på tunnel: %s</string>
<string name="tunnel_rename_success">Endret navn på tunnelen til «%s»</string>
<string name="type_name_go_userspace">Bruk userspace</string>
<string name="type_name_kernel_module">Kjernemodul</string>
<string name="unknown_error">Ukjent feil</string>
<string name="version_summary">%1$s backend %2$s</string>
<string name="version_summary_checking">Sjekker %s backend versjon</string>
<string name="version_summary_unknown">Ukjent %s versjon</string>
<string name="version_title">WireGuard for Android v%s</string>
+7
View File
@@ -133,6 +133,9 @@
<string name="tv_select_a_storage_drive">Выберите накопитель</string>
<string name="tv_no_file_picker">Пожалуйста, установите утилиту управления файлами для их просмотра</string>
<string name="tv_add_tunnel_get_started">Добавьте туннель, чтобы начать</string>
<string name="donate_title">♥ Пожертвовать проекту WireGuard</string>
<string name="donate_summary">Каждое пожертвование помогает</string>
<string name="donate_google_play_disappointment">Спасибо за поддержку проекта WireGuard!\n\nК сожалению, из-за политики Google, нельзя размещать ссылку на тот раздел сайта проекта, где можно сделать пожертвование. Надеемся, вы сможете разобраться самостоятельно!\n\nЕщё раз спасибо за ваш вклад.</string>
<string name="disable_config_export_title">Отключить экспорт конфигурации</string>
<string name="disable_config_export_description">Отключение экспорта конфигурации делает приватные ключи менее доступными</string>
<string name="dns_servers">DNS-серверы</string>
@@ -142,6 +145,7 @@
<string name="error_down">Ошибка при выходе из туннеля: %s</string>
<string name="error_fetching_apps">Ошибка при получении списка приложений: %s</string>
<string name="error_root">Пожалуйста, получите root-доступ и попробуйте снова</string>
<string name="error_prepare">Ошибка при подготовке туннеля: %s</string>
<string name="error_up">Ошибка при запуске туннеля: %s</string>
<string name="exclude_private_ips">Исключить частные IP-адреса</string>
<string name="generate_new_private_key">Сгенерировать новый приватный ключ</string>
@@ -161,6 +165,8 @@
<string name="key_length_explanation_base64">: ключи WireGuard base64 должны содержать 44 символа (32 байта)</string>
<string name="key_length_explanation_binary">: ключи WireGuard должны быть 32 байта</string>
<string name="key_length_explanation_hex">: HEX-ключи WireGuard должны содержать 64 символа (32 байта)</string>
<string name="latest_handshake">Последнее рукопожатие</string>
<string name="latest_handshake_ago">%s назад</string>
<string name="listen_port">Порт</string>
<string name="log_export_error">Не удалось экспортировать журнал: %s</string>
<string name="log_export_subject">Файл журнала WireGuard Android</string>
@@ -239,6 +245,7 @@
<string name="tunnel_create_success">Успешно создан туннель “%s”</string>
<string name="tunnel_error_already_exists">Туннель “%s” уже существует</string>
<string name="tunnel_error_invalid_name">Неправильное имя</string>
<string name="tunnel_list_placeholder">Добавьте туннель с помощью кнопки ниже</string>
<string name="tunnel_name">Название туннеля</string>
<string name="tunnel_on_error">Не удалось включить туннель (wgTurnOn вернул %d)</string>
<string name="tunnel_dns_failure">Не удалось определить DNS имя: “%s”</string>
@@ -107,6 +107,9 @@
<string name="tv_select_a_storage_drive">Bir depolama sürücüsü seçin</string>
<string name="tv_no_file_picker">Dosyalara göz atmak için lütfen bir dosya yönetim aracı yükleyin</string>
<string name="tv_add_tunnel_get_started">Başlamak için bir tünel ekleyin</string>
<string name="donate_title">♥ WireGuard Projesine Bağış Yapın</string>
<string name="donate_summary">Her katkı yardımcı olur</string>
<string name="donate_google_play_disappointment">WireGuard Projesini desteklediğiniz için teşekkür ederiz!\n\nNe yazık ki, Google\'ın politikaları nedeniyle, proje web sayfasının bağış yapabileceğiniz bölümüne bağlantı vermemize izin verilmiyor. Umarım bunu çözebilirsin!\n\nKatkılarınız için tekrar teşekkürler.</string>
<string name="disable_config_export_title">Ayarları dışa aktarmayı kapa</string>
<string name="disable_config_export_description">Ayarları dışa aktarmayı kapamak gizli anahtarları daha az erişilebilir kılar</string>
<string name="dns_servers">DNS sunucuları</string>
@@ -213,6 +216,7 @@
<string name="tunnel_create_success">Tünel başarıyla oluşturuldu “%s”</string>
<string name="tunnel_error_already_exists">“%s” tüneli zaten mevcut</string>
<string name="tunnel_error_invalid_name">Geçersiz isim</string>
<string name="tunnel_list_placeholder">Aşağıdaki düğmeyi kullanarak bir tünel ekleyin</string>
<string name="tunnel_name">Tünel İsmi</string>
<string name="tunnel_on_error">Tünel aktif edilemiyor (wgTornOn döndürdü: %d)</string>
<string name="tunnel_dns_failure">DNS adı çözülemedi: \"%s\"</string>
+9 -2
View File
@@ -94,6 +94,9 @@
<string name="tv_select_a_storage_drive">选择一个存储驱动器</string>
<string name="tv_no_file_picker">请安装一个文件管理工具以浏览文件</string>
<string name="tv_add_tunnel_get_started">添加第一条网络隧道</string>
<string name="donate_title">♥ 为 WireGuard 捐赠</string>
<string name="donate_summary">无论多寡,聚沙成塔</string>
<string name="donate_google_play_disappointment">感谢您对 WireGuard 项目的支持!\n\n只可惜,受谷歌的政策所限,我们不能在此展示项目捐赠页面的链接,还望您自行访问捐赠页面!\n\n再次感谢您的贡献。</string>
<string name="disable_config_export_title">禁止导出配置</string>
<string name="disable_config_export_description">禁止导出配置可降低私钥泄露的风险</string>
<string name="dns_servers">DNS 服务器</string>
@@ -103,6 +106,7 @@
<string name="error_down">断开连接时出错:%s</string>
<string name="error_fetching_apps">获取应用列表时出错:%s</string>
<string name="error_root">请获取 root 权限并重试</string>
<string name="error_prepare">准备连接时出错:%s</string>
<string name="error_up">建立连接时出错:%s</string>
<string name="exclude_private_ips">排除局域网</string>
<string name="generate_new_private_key">生成新的私钥</string>
@@ -122,6 +126,8 @@
<string name="key_length_explanation_base64">:WireGuard 的 Base64 密钥长度必须为 44 个字符(32 字节)</string>
<string name="key_length_explanation_binary">:WireGuard 密钥大小必须为 32 字节</string>
<string name="key_length_explanation_hex">:WireGuard 的十六进制密钥长度必须为 64 个字符(32 字节)</string>
<string name="latest_handshake">上次握手时间</string>
<string name="latest_handshake_ago">%s之前</string>
<string name="listen_port">监听端口</string>
<string name="log_export_error">无法导出日志:%s</string>
<string name="log_export_subject">WireGuard 日志文件</string>
@@ -132,7 +138,7 @@
<string name="log_viewer_pref_title">查看应用日志</string>
<string name="log_viewer_title">日志</string>
<string name="logcat_error">无法运行 logcat: </string>
<string name="module_enabler_disabled_summary">WireGuard-Andorid 的内核模块尚处于实验阶段。它能够增强性能,但存在潜在的不稳定因素,请谨慎开启</string>
<string name="module_enabler_disabled_summary">内核模块(实验性)能够增强性能,启用时需谨慎</string>
<string name="module_enabler_disabled_title">启用内核模块</string>
<string name="module_enabler_enabled_summary">用户空间的模块性能较弱,但稳定性更好</string>
<string name="module_enabler_enabled_title">停用内核模块</string>
@@ -200,6 +206,7 @@
<string name="tunnel_create_success">成功创建隧道 “%s”</string>
<string name="tunnel_error_already_exists">名称 “%s” 已存在</string>
<string name="tunnel_error_invalid_name">名称无效</string>
<string name="tunnel_list_placeholder">点击下方按钮添加隧道</string>
<string name="tunnel_name">隧道名称</string>
<string name="tunnel_on_error">无法开启隧道(wgTurnOn returned %d)</string>
<string name="tunnel_dns_failure">无法解析 DNS 主机名:“%s”</string>
@@ -208,7 +215,7 @@
<string name="type_name_go_userspace">Go userspace</string>
<string name="type_name_kernel_module">Kernel module</string>
<string name="unknown_error">未知错误</string>
<string name="version_summary">%1$s 后端 %2$s</string>
<string name="version_summary">%1$s backend %2$s</string>
<string name="version_summary_checking">正在检查 %s backend 版本</string>
<string name="version_summary_unknown">未知的 %s 版本</string>
<string name="version_title">WireGuard for Android v%s</string>
+4 -1
View File
@@ -2,6 +2,9 @@
<resources>
<declare-styleable name="Multiselected">
<attr name="state_multiselected" format="boolean" />
<attr name="colorMultiselectActiveBackground" format="reference|color" />
</declare-styleable>
<declare-styleable name="TvCardView">
<attr name="state_isUp" format="boolean" />
<attr name="state_isDeleting" format="boolean" />
</declare-styleable>
</resources>
-1
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="extra_margin">12dp</dimen>
<dimen name="bottom_sheet_item_height">56dp</dimen>
<dimen name="normal_margin">8dp</dimen>
<dimen name="bottom_sheet_top_padding">8dp</dimen>
+3
View File
@@ -120,6 +120,7 @@
<string name="error_down">Error bringing down tunnel: %s</string>
<string name="error_fetching_apps">Error fetching apps list: %s</string>
<string name="error_root">Please obtain root access and try again</string>
<string name="error_prepare">Error preparing tunnel: %s</string>
<string name="error_up">Error bringing up tunnel: %s</string>
<string name="exclude_private_ips">Exclude private IPs</string>
<string name="generate_new_private_key">Generate new private key</string>
@@ -139,6 +140,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 keys must be 44 characters (32 bytes)</string>
<string name="key_length_explanation_binary">: WireGuard keys must be 32 bytes</string>
<string name="key_length_explanation_hex">: WireGuard hex keys must be 64 characters (32 bytes)</string>
<string name="latest_handshake">Latest handshake</string>
<string name="latest_handshake_ago">%s ago</string>
<string name="listen_port">Listen port</string>
<string name="log_export_error">Unable to export log: %s</string>
<string name="log_export_subject">WireGuard Android Log File</string>
+7 -1
View File
@@ -9,10 +9,11 @@
<item name="materialCardViewStyle">@style/WireGuardTheme.MaterialCardView</item>
<item name="toolbarStyle">@style/WireGuardTheme.Toolbar</item>
<item name="bottomSheetDialogTheme">@style/WireGuardTheme.BottomSheetDialog</item>
<item name="android:statusBarColor">@null</item>
</style>
<!-- Various additional API-specific features in values-v*/styles.xml -->
<style name="AppTheme" parent="AppThemeBase"/>
<style name="AppTheme" parent="AppThemeBase" />
<style name="WireGuardTheme.MaterialCardView" parent="Widget.Material3.CardView.Elevated">
<item name="cornerRadius">4dp</item>
@@ -36,4 +37,9 @@
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>
<style name="TvTheme" parent="AppTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>
-1
View File
@@ -27,6 +27,5 @@
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_inversePrimary</item>
<item name="colorMultiselectActiveBackground">@color/md_theme_light_surfaceVariant</item>
</style>
</resources>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="tv_primary_color">#ff212121</color>
<color name="tv_secondary_dark_color">#ff0059c1</color>
<color name="tv_card_background">@color/tv_primary_color</color>
<color name="tv_card_delete_background">#b00020</color>
<color name="tv_list_multiselect_background">#ffeeeeee</color>
</resources>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="TvTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/tv_primary_color</item>
<item name="colorOnPrimary">#fffafafa</item>
<item name="colorPrimaryDark">@color/tv_primary_color</item>
<item name="colorPrimaryVariant">#ff484848</item>
<item name="colorSecondary">#ff4285f4</item>
<item name="colorOnSecondary">#ff0059c1</item>
<item name="colorSurface">@color/tv_primary_color</item>
<item name="colorOnSurface">#fffafafa</item>
<item name="colorMultiselectActiveBackground">@color/tv_list_multiselect_background</item>
<item name="colorControlNormal">#fffafafa</item>
<item name="elevationOverlayEnabled">false</item>
<item name="android:statusBarColor">@color/tv_primary_color</item>
<item name="android:windowBackground">@color/tv_primary_color</item>
<item name="alertDialogTheme">@style/TvTheme.Dialog</item>
<item name="materialAlertDialogTheme">@style/TvTheme.Dialog</item>
<item name="materialCardViewStyle">@style/TvTheme.MaterialCardView</item>
</style>
<style name="TvTheme.Dialog" parent="Theme.Material3.DayNight.Dialog.Alert">
<item name="android:windowBackground">?attr/colorSurface</item>
</style>
<style name="TvTheme.MaterialCardView" parent="Widget.Material3.CardView.Elevated">
<item name="cornerRadius">4dp</item>
<item name="contentPadding">8dp</item>
</style>
</resources>
+2 -2
View File
@@ -1,6 +1,6 @@
buildscript {
ext {
wireguardVersionCode = 497
wireguardVersionName = '1.0.20230330'
wireguardVersionCode = 500
wireguardVersionName = '1.0.20230427'
}
}