Compare commits

...

29 Commits

Author SHA1 Message Date
Jason A. Donenfeld fbaa4d9ab1 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-12 02:14:25 +02:00
Harsh Shandilya 19b57c41b7 Address lint issues in TunnelListFragment
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-12 02:07:19 +02:00
Jason A. Donenfeld 26d762bc5c TunnelEditorFragment: add DNSes to allowedIPs when excluding rfc1918
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-12 02:04:52 +02:00
Jason A. Donenfeld eab0248aaa Clean up warnings
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-11 21:03:22 +02:00
Jason A. Donenfeld a5bbe171cb fab: remove asus hack
Let's hope Eric's changes make this no longer required.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-11 21:03:22 +02:00
Eric Kuck 5463086e75 fab: use support library's rendering
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-11 21:03:22 +02:00
Jason A. Donenfeld 1f7bdd4f5f Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-10 22:00:10 +02:00
Jason A. Donenfeld 3cf6aad083 QuickTileService: automatically slash the tile
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-09 18:55:55 +02:00
Jason A. Donenfeld b997a2581b BaseFragment: in the event no view is available, use toast
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-09 17:29:35 +02:00
Eric Kuck d7ea078cdf Request VPN permissions on activation
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-09 17:08:41 +02:00
Jason A. Donenfeld d50e0f5fb9 Use instanceOf instead of getClass
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-08 23:59:25 +02:00
Eric Kuck c696e9f275 Build with different name and ID in debug mode
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-08 03:26:42 +02:00
Jason A. Donenfeld 707c8c19a8 gradle: bump build tools version
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-08 03:18:15 +02:00
Eric Kuck b37b48b8dc Switch from ListView to RecyclerView
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-08 02:50:49 +02:00
Jason A. Donenfeld 2c7203ab8d Another bump for misbuild
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 21:13:36 +02:00
Jason A. Donenfeld d1a812042c Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 18:17:58 +02:00
Jason A. Donenfeld 78d976162d PeerEditor: put exclusion checkbox next to label
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 18:17:58 +02:00
Jason A. Donenfeld 7078162c69 AppListDialogFragment: add deselect all button
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 18:17:58 +02:00
Jason A. Donenfeld 2742b09b5a tools: update wg-quick for ExcludedApplications support
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 18:17:58 +02:00
Jason A. Donenfeld 7b28d51cdd global: move to Apache 2.0
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 16:28:46 +02:00
Jason A. Donenfeld d132087b3c PeerEditor: add exclude private IPs functionality
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 16:28:46 +02:00
Jason A. Donenfeld 124f186983 TunnelEditor: fix nits
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 16:28:46 +02:00
Eric Kuck 500a705531 AppListDialogFragment: add implementation for excluding applications
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-06 04:14:19 +02:00
Jason A. Donenfeld 5729947d6c TunnelEditor: better looking buttons
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-06 02:40:11 +02:00
Harsh Shandilya 363d0b9126 android: model: Make some methods static
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-06-30 21:19:59 +02:00
Harsh Shandilya e985452f3b android: FloatingActionsMenu: Don't create labels on ASUS' Android 5 devices
They have completely wrecked the framework there and all efforts to
work around their absolutely broken software have been in vain, hence
let's atleast let users be able to use the app, labels or otherwise.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-06-30 21:10:52 +02:00
Harsh Shandilya 5c9643a23b android: VersionPreference: Handle no-browser-installed case
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-06-30 21:10:22 +02:00
Harsh Shandilya 0e3e3ae37b android: Add select all button to action mode
Thanks to Jason for suggesting the not-clinically-insane
method to go about this.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-06-30 21:07:24 +02:00
Harsh Shandilya b41d473f64 wireguard: Bump snapshot
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-06-30 21:06:40 +02:00
97 changed files with 1410 additions and 1258 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright © &amp;#36;today.year Firstname Lastname &lt;email@example.org&gt;&#10;SPDX-License-Identifier:" />
<option name="notice" value="Copyright © &amp;#36;today.year Firstname Lastname &lt;email@example.org&gt;&#10;SPDX-License-Identifier: Apache-2.0
<option name="myName" value="Default" />
</copyright>
</component>
-6
View File
@@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="myName" value="GPL-2.0-or-later" />
<option name="notice" value="Copyright © &amp;#36;today.year Firstname Lastname &lt;email@example.com&gt;&#10;SPDX-License-Identifier: GPL-2.0-or-later" />
</copyright>
</component>
+169 -305
View File
@@ -1,338 +1,202 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Preamble
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
1. Definitions.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
The precise terms and conditions for copying, distribution and
modification follow.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
END OF TERMS AND CONDITIONS
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
APPENDIX: How to apply the Apache License to your work.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
Copyright [yyyy] [name of copyright owner]
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
http://www.apache.org/licenses/LICENSE-2.0
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-16
View File
@@ -13,19 +13,3 @@ $ git submodule init
$ git submodule update
$ ./gradlew assembleRelease
```
## License
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+7 -3
View File
@@ -5,7 +5,7 @@ apply plugin: 'com.android.application'
final def keystorePropertiesFile = rootProject.file("keystore.properties")
android {
buildToolsVersion '28.0.0'
buildToolsVersion '28.0.1'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -16,8 +16,8 @@ android {
applicationId 'com.wireguard.android'
minSdkVersion 21
targetSdkVersion 27
versionCode 427
versionName '0.0.20180625'
versionCode 432
versionName '0.0.20180711'
}
// If the keystore file exists
if (keystorePropertiesFile.exists()) {
@@ -40,6 +40,10 @@ android {
release {
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
}
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
}
externalNativeBuild {
cmake {
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">WireGuard β</string>
</resources>
+2 -2
View File
@@ -9,7 +9,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<permission
android:name="com.wireguard.android.permission.CONTROL_TUNNELS"
android:name="${applicationId}.permission.CONTROL_TUNNELS"
android:protectionLevel="dangerous"
android:icon="@mipmap/ic_launcher"
android:label="@string/permission_label"
@@ -59,7 +59,7 @@
<receiver
android:name=".model.TunnelManager$IntentReceiver"
android:permission="com.wireguard.android.permission.CONTROL_TUNNELS">
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
<intent-filter>
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android;
@@ -25,7 +25,7 @@ import com.wireguard.android.util.ToolsInstaller;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.concurrent.Executor;
public class Application extends android.app.Application {
@@ -37,7 +37,7 @@ public class Application extends android.app.Application {
private ToolsInstaller toolsInstaller;
private TunnelManager tunnelManager;
private Handler handler;
private List<BackendCallback> haveBackendCallbacks = new ArrayList<>();
private Collection<BackendCallback> haveBackendCallbacks = new ArrayList<>();
private final Object haveBackendCallbacksLock = new Object();
public Application() {
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android;
@@ -21,7 +21,7 @@ public class BootShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
Application.onHaveBackend(backend -> {
if (backend.getClass() != WgQuickBackend.class)
if (!(backend instanceof WgQuickBackend))
return;
final String action = intent.getAction();
if (action == null)
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android;
@@ -10,6 +10,10 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.databinding.Observable;
import android.databinding.Observable.OnPropertyChangedCallback;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.service.quicksettings.Tile;
@@ -21,6 +25,7 @@ import com.wireguard.android.activity.MainActivity;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.widget.SlashDrawable;
import java.util.Objects;
@@ -37,6 +42,27 @@ public class QuickTileService extends TileService {
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
private Tunnel tunnel;
private Icon iconOn;
private Icon iconOff;
@SuppressWarnings("deprecation")
@Override
public void onCreate() {
final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile));
icon.setAnimationEnabled(false); /* Unfortunately we can't have animations, since Icons are marshaled. */
icon.setSlashed(false);
Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
icon.draw(c);
iconOn = Icon.createWithBitmap(b);
icon.setSlashed(true);
b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
c = new Canvas(b);
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
icon.draw(c);
iconOff = Icon.createWithBitmap(b);
}
@Override
public void onClick() {
@@ -99,10 +125,7 @@ public class QuickTileService extends TileService {
return;
tile.setLabel(label);
if (tile.getState() != state) {
// The icon must be changed every time the state changes, or the shade will not change.
final Integer iconResource = state == Tile.STATE_ACTIVE ? R.drawable.ic_tile
: R.drawable.ic_tile_disabled;
tile.setIcon(Icon.createWithResource(this, iconResource));
tile.setIcon(state == Tile.STATE_ACTIVE ? iconOn : iconOff);
tile.setState(state);
}
tile.updateTile();
@@ -1,18 +1,16 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity;
import android.content.Intent;
import android.databinding.CallbackRegistry;
import android.databinding.CallbackRegistry.NotifierCallback;
import android.os.Bundle;
import com.wireguard.android.Application;
import com.wireguard.android.backend.GoBackend;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.TunnelManager;
@@ -52,14 +50,6 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
// The selected tunnel must be set before the superclass method recreates fragments.
super.onCreate(savedInstanceState);
Application.onHaveBackend(backend -> {
if (backend.getClass() == GoBackend.class) {
final Intent intent = GoBackend.VpnService.prepare(this);
if (intent != null)
startActivityForResult(intent, 0);
}
});
}
@Override
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity;
@@ -102,7 +102,7 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
final PreferenceScreen screen = getPreferenceScreen();
Application.onHaveBackend(backend -> {
for (final Preference pref : wgQuickOnlyPrefs) {
if (backend.getClass() == WgQuickBackend.class)
if (backend instanceof WgQuickBackend)
pref.setVisible(true);
else
screen.removePreference(pref);
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.backend;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.backend;
@@ -171,6 +171,9 @@ public final class GoBackend implements Backend {
configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
builder.setConfigureIntent(PendingIntent.getActivity(context, 0, configureIntent, 0));
for (final String excludedApplication : config.getInterface().getExcludedApplications())
builder.addDisallowedApplication(excludedApplication);
for (final InetNetwork addr : config.getInterface().getAddresses())
builder.addAddress(addr.getAddress(), addr.getMask());
@@ -250,5 +253,6 @@ public final class GoBackend implements Backend {
}
return super.onStartCommand(intent, flags, startId);
}
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.backend;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.configStore;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.configStore;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding;
@@ -9,16 +9,18 @@ package com.wireguard.android.databinding;
import android.databinding.BindingAdapter;
import android.databinding.ObservableList;
import android.databinding.adapters.ListenerUtil;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.InputFilter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.wireguard.android.R;
import com.wireguard.util.Keyed;
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler;
import com.wireguard.android.util.ObservableKeyedList;
import com.wireguard.android.widget.ToggleSwitch;
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
import com.wireguard.util.Keyed;
/**
* Static methods for use by generated code in the Android data binding library.
@@ -65,16 +67,19 @@ public final class BindingAdapters {
listener.setList(newList);
}
@BindingAdapter({"items", "layout"})
@BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
public static <K, E extends Keyed<? extends K>>
void setItems(final ListView view,
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
void setItems(final RecyclerView view,
final ObservableKeyedList<K, E> oldList, final int oldLayoutId, final RowConfigurationHandler oldRowConfigurationHandler,
final ObservableKeyedList<K, E> newList, final int newLayoutId, final RowConfigurationHandler newRowConfigurationHandler) {
if (view.getLayoutManager() == null)
view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
if (oldList == newList && oldLayoutId == newLayoutId)
return;
// The ListAdapter interface is not generic, so this cannot be checked.
@SuppressWarnings("unchecked") ObservableKeyedListAdapter<K, E> adapter =
(ObservableKeyedListAdapter<K, E>) view.getAdapter();
@SuppressWarnings("unchecked") ObservableKeyedRecyclerViewAdapter<K, E> adapter =
(ObservableKeyedRecyclerViewAdapter<K, E>) view.getAdapter();
// If the layout changes, any existing adapter must be replaced.
if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
adapter.setList(null);
@@ -84,9 +89,11 @@ public final class BindingAdapters {
if (newList == null || newLayoutId == 0)
return;
if (adapter == null) {
adapter = new ObservableKeyedListAdapter<>(view.getContext(), newLayoutId, newList);
adapter = new ObservableKeyedRecyclerViewAdapter<>(view.getContext(), newLayoutId, newList);
view.setAdapter(adapter);
}
adapter.setRowConfigurationHandler(newRowConfigurationHandler);
// Either the list changed, or this is an entirely new listener because the layout changed.
adapter.setList(newList);
}
@@ -96,4 +103,5 @@ public final class BindingAdapters {
final OnBeforeCheckedChangeListener listener) {
view.setOnBeforeCheckedChangeListener(listener);
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding;
@@ -10,41 +10,43 @@ import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.ObservableList;
import android.databinding.ViewDataBinding;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.wireguard.android.BR;
import com.wireguard.util.Keyed;
import com.wireguard.android.util.ObservableKeyedList;
import com.wireguard.util.Keyed;
import java.lang.ref.WeakReference;
/**
* A generic {@code ListAdapter} backed by a {@code ObservableKeyedList}.
* A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
*/
class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAdapter {
public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extends Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder> {
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
private final int layoutId;
private final LayoutInflater layoutInflater;
private ObservableKeyedList<K, E> list;
private RowConfigurationHandler rowConfigurationHandler;
ObservableKeyedListAdapter(final Context context, final int layoutId,
final ObservableKeyedList<K, E> list) {
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
final ObservableKeyedList<K, E> list) {
this.layoutId = layoutId;
layoutInflater = LayoutInflater.from(context);
setList(list);
}
@Override
public int getCount() {
public int getItemCount() {
return list != null ? list.size() : 0;
}
@Override
public E getItem(final int position) {
private E getItem(final int position) {
if (list == null || position < 0 || position >= list.size())
return null;
return list.get(position);
@@ -61,21 +63,22 @@ class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAd
return item != null ? item.getKey() : null;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
if (binding == null)
binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false);
binding.setVariable(BR.collection, list);
binding.setVariable(BR.key, getKey(position));
binding.setVariable(BR.item, getItem(position));
binding.executePendingBindings();
return binding.getRoot();
@NonNull @Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
}
@SuppressWarnings("unchecked")
@Override
public boolean hasStableIds() {
return true;
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
holder.binding.setVariable(BR.collection, list);
holder.binding.setVariable(BR.key, getKey(position));
holder.binding.setVariable(BR.item, getItem(position));
holder.binding.executePendingBindings();
if (rowConfigurationHandler != null) {
rowConfigurationHandler.onConfigureRow(holder.binding, getItem(position), position);
}
}
void setList(final ObservableKeyedList<K, E> newList) {
@@ -88,18 +91,22 @@ class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAd
notifyDataSetChanged();
}
void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
this.rowConfigurationHandler = rowConfigurationHandler;
}
private static final class OnListChangedCallback<E extends Keyed<?>>
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
private final WeakReference<ObservableKeyedListAdapter<?, E>> weakAdapter;
private final WeakReference<ObservableKeyedRecyclerViewAdapter<?, E>> weakAdapter;
private OnListChangedCallback(final ObservableKeyedListAdapter<?, E> adapter) {
private OnListChangedCallback(final ObservableKeyedRecyclerViewAdapter<?, E> adapter) {
weakAdapter = new WeakReference<>(adapter);
}
@Override
public void onChanged(final ObservableList<E> sender) {
final ObservableKeyedListAdapter adapter = weakAdapter.get();
final ObservableKeyedRecyclerViewAdapter adapter = weakAdapter.get();
if (adapter != null)
adapter.notifyDataSetChanged();
else
@@ -130,4 +137,19 @@ class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAd
onChanged(sender);
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
final ViewDataBinding binding;
public ViewHolder(final ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
void onConfigureRow(B binding, T item, int position);
}
}
@@ -0,0 +1,141 @@
/*
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.widget.Toast;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.activity.BaseActivity;
import com.wireguard.android.databinding.AppListDialogFragmentBinding;
import com.wireguard.android.model.ApplicationData;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.ObservableKeyedArrayList;
import com.wireguard.android.util.ObservableKeyedList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class AppListDialogFragment extends DialogFragment {
private static final String KEY_EXCLUDED_APPS = "excludedApps";
private List<String> currentlyExcludedApps;
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) {
final Bundle extras = new Bundle();
extras.putStringArray(KEY_EXCLUDED_APPS, excludedApps);
final AppListDialogFragment fragment = new AppListDialogFragment();
fragment.setTargetFragment(target, 0);
fragment.setArguments(extras);
return fragment;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
}
@Override
public void onAttach(final Context context) {
super.onAttach(context);
}
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.excluded_applications);
AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
binding.executePendingBindings();
alertDialogBuilder.setView(binding.getRoot());
alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss());
alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> { });
binding.setFragment(this);
binding.setAppData(appData);
loadData();
final AlertDialog dialog = alertDialogBuilder.create();
dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> {
for (final ApplicationData app : appData)
app.setExcludedFromTunnel(false);
}));
return dialog;
}
private void loadData() {
final Activity activity = getActivity();
if (activity == null) {
return;
}
final PackageManager pm = activity.getPackageManager();
Application.getAsyncWorker().supplyAsync(() -> {
final Intent launcherIntent = new Intent(Intent.ACTION_MAIN, null);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(launcherIntent, 0);
final List<ApplicationData> appData = new ArrayList<>();
for (ResolveInfo resolveInfo : resolveInfos) {
String packageName = resolveInfo.activityInfo.packageName;
appData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)));
}
Collections.sort(appData, (lhs, rhs) -> lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()));
return appData;
}).whenComplete(((data, throwable) -> {
if (data != null) {
appData.clear();
appData.addAll(data);
} else {
final String error = throwable != null ? ExceptionLoggers.unwrapMessage(throwable) : "Unknown";
final String message = activity.getString(R.string.error_fetching_apps, error);
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
dismissAllowingStateLoss();
}
}));
}
void setExclusionsAndDismiss() {
final List<String> excludedApps = new ArrayList<>();
for (final ApplicationData data : appData) {
if (data.isExcludedFromTunnel()) {
excludedApps.add(data.getPackageName());
}
}
((AppExclusionListener) getTargetFragment()).onExcludedAppsSelected(excludedApps);
dismiss();
}
public interface AppExclusionListener {
void onExcludedAppsSelected(List<String> excludedApps);
}
}
@@ -1,17 +1,32 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.activity.BaseActivity;
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
import com.wireguard.android.backend.GoBackend;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.util.ExceptionLoggers;
/**
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when
@@ -19,7 +34,12 @@ import com.wireguard.android.model.Tunnel;
*/
public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
private BaseActivity activity;
private Tunnel pendingTunnel;
private Boolean pendingTunnelUp;
protected Tunnel getSelectedTunnel() {
return activity != null ? activity.getSelectedTunnel() : null;
@@ -44,8 +64,62 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
super.onDetach();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
if (pendingTunnel != null && pendingTunnelUp != null)
setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp);
pendingTunnel = null;
pendingTunnelUp = null;
}
}
protected void setSelectedTunnel(final Tunnel tunnel) {
if (activity != null)
activity.setSelectedTunnel(tunnel);
}
public void setTunnelState(final View view, final boolean checked) {
final ViewDataBinding binding = DataBindingUtil.findBinding(view);
final Tunnel tunnel;
if (binding instanceof TunnelDetailFragmentBinding)
tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
else if (binding instanceof TunnelListItemBinding)
tunnel = ((TunnelListItemBinding) binding).getItem();
else
return;
Application.onHaveBackend(backend -> {
if (backend instanceof GoBackend) {
final Intent intent = GoBackend.VpnService.prepare(view.getContext());
if (intent != null) {
pendingTunnel = tunnel;
pendingTunnelUp = checked;
startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
return;
}
}
setTunnelStateWithPermissionsResult(tunnel, checked);
});
}
private void setTunnelStateWithPermissionsResult(@NonNull final Tunnel tunnel, final boolean checked) {
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
if (throwable == null)
return;
final String error = ExceptionLoggers.unwrapMessage(throwable);
final int messageResId = checked ? R.string.error_up : R.string.error_down;
final String message = getContext().getString(messageResId, error);
final View view = getView();
if (view != null)
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
else
Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
Log.e(TAG, message, throwable);
});
}
}
@@ -1,58 +0,0 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
package com.wireguard.android.fragment;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.View;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.util.ExceptionLoggers;
/**
* Helper method shared by TunnelListFragment and TunnelDetailFragment.
*/
public final class TunnelController {
private static final String TAG = "WireGuard/" + TunnelController.class.getSimpleName();
private TunnelController() {
// Prevent instantiation.
}
public static void setTunnelState(final View view, final boolean checked) {
final ViewDataBinding binding = DataBindingUtil.findBinding(view);
final Tunnel tunnel;
if (binding instanceof TunnelDetailFragmentBinding)
tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
else if (binding instanceof TunnelListItemBinding)
tunnel = ((TunnelListItemBinding) binding).getItem();
else
tunnel = null;
if (tunnel == null) {
Log.e(TAG, "setChecked() from a null tunnel", new IllegalStateException("No tunnel"));
return;
}
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
if (throwable == null)
return;
final Context context = view.getContext();
final String error = ExceptionLoggers.unwrapMessage(throwable);
final int messageResId = checked ? R.string.error_up : R.string.error_down;
final String message = context.getString(messageResId, error);
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
Log.e(TAG, message, throwable);
});
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
@@ -69,7 +69,9 @@ public class TunnelDetailFragment extends BaseFragment {
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
binding.setFragment(this);
onSelectedTunnelChanged(null, getSelectedTunnel());
super.onViewStateRestored(savedInstanceState);
}
}
@@ -1,16 +1,19 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
import android.app.Activity;
import android.content.Context;
import android.databinding.Observable;
import android.databinding.ObservableList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -22,18 +25,24 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import com.wireguard.android.Application;
import com.wireguard.android.BR;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.TunnelManager;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.config.Attribute;
import com.wireguard.config.Config;
import com.wireguard.config.Peer;
import java.util.List;
/**
* Fragment for editing a WireGuard configuration.
*/
public class TunnelEditorFragment extends BaseFragment {
public class TunnelEditorFragment extends BaseFragment implements AppExclusionListener {
private static final String KEY_LOCAL_CONFIG = "local_config";
private static final String KEY_ORIGINAL_NAME = "original_name";
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
@@ -74,11 +83,52 @@ public class TunnelEditorFragment extends BaseFragment {
inflater.inflate(R.menu.config_editor, menu);
}
private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() {
@Override
public void onChanged(final ObservableList<Peer.Observable> sender) { }
@Override
public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { }
@Override
public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) { }
@Override
public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
@Override
public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
};
private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(final Observable sender, final int propertyId) {
final Config.Observable config = binding.getConfig();
if (config == null)
return;
if (propertyId == BR.config) {
config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
} else if (propertyId == BR.dnses || propertyId == BR.peers)
;
else
return;
final int numSiblings = config.getPeers().size() - 1;
for (final Peer.Observable peer : config.getPeers()) {
peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses());
peer.setNumSiblings(numSiblings);
}
}
};
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
binding.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
binding.executePendingBindings();
return binding.getRoot();
}
@@ -202,6 +252,8 @@ public class TunnelEditorFragment extends BaseFragment {
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
binding.setFragment(this);
if (savedInstanceState == null) {
onSelectedTunnelChanged(null, getSelectedTunnel());
} else {
@@ -216,4 +268,19 @@ public class TunnelEditorFragment extends BaseFragment {
super.onViewStateRestored(savedInstanceState);
}
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
fragment.show(getFragmentManager(), null);
}
}
@Override
public void onExcludedAppsSelected(final List<String> excludedApps) {
binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps));
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
@@ -16,27 +16,23 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.activity.TunnelCreatorActivity;
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter;
import com.wireguard.android.databinding.TunnelListFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.config.Config;
@@ -47,13 +43,12 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java9.util.concurrent.CompletableFuture;
import java9.util.stream.Collectors;
import java9.util.stream.IntStream;
import java9.util.stream.StreamSupport;
/**
@@ -64,8 +59,7 @@ public class TunnelListFragment extends BaseFragment {
private static final int REQUEST_IMPORT = 1;
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
private final MultiChoiceModeListener actionModeListener = new ActionModeListener();
private final ListViewCallbacks listViewCallbacks = new ListViewCallbacks();
private final ActionModeListener actionModeListener = new ActionModeListener();
private ActionMode actionMode;
private TunnelListFragmentBinding binding;
@@ -182,20 +176,19 @@ public class TunnelListFragment extends BaseFragment {
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
binding.tunnelList.setMultiChoiceModeListener(actionModeListener);
binding.tunnelList.setOnItemClickListener(listViewCallbacks);
binding.tunnelList.setOnItemLongClickListener(listViewCallbacks);
binding.tunnelList.setOnTouchListener(listViewCallbacks);
binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
if (binding != null) {
binding.createMenu.collapse();
}
return false;
});
binding.executePendingBindings();
return binding.getRoot();
}
@@ -276,35 +269,53 @@ public class TunnelListFragment extends BaseFragment {
super.onViewStateRestored(savedInstanceState);
binding.setFragment(this);
binding.setTunnels(Application.getTunnelManager().getTunnels());
binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
binding.setFragment(this);
binding.getRoot().setOnClickListener(clicked -> {
if (actionMode == null) {
setSelectedTunnel(tunnel);
} else {
actionModeListener.toggleItemChecked(position);
}
});
binding.getRoot().setOnLongClickListener(clicked -> {
actionModeListener.toggleItemChecked(position);
return true;
});
binding.getRoot().setActivated(actionModeListener.checkedItems.contains(position));
});
}
private final class ActionModeListener implements MultiChoiceModeListener {
private Resources resources;
private AbsListView tunnelList;
private final class ActionModeListener implements ActionMode.Callback {
private final Collection<Integer> checkedItems = new HashSet<>();
private IntStream getCheckedPositions() {
final SparseBooleanArray checkedItemPositions = tunnelList.getCheckedItemPositions();
return IntStream.range(0, checkedItemPositions.size())
.filter(checkedItemPositions::valueAt)
.map(checkedItemPositions::keyAt);
}
private Resources resources;
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_action_delete:
// Must operate in two steps: positions change once we start deleting things.
final List<Tunnel> tunnelsToDelete = getCheckedPositions()
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
.collect(Collectors.toList());
final Collection<Tunnel> tunnelsToDelete = new ArrayList<>();
for (final Integer position : checkedItems) {
tunnelsToDelete.add(Application.getTunnelManager().getTunnels().get(position));
}
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
.map(Tunnel::delete)
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures)
.thenApply(x -> futures.length)
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
checkedItems.clear();
mode.finish();
return true;
case R.id.menu_action_select_all:
for (int i = 0; i < Application.getTunnelManager().getTunnels().size(); ++i) {
setItemChecked(i, true);
}
return true;
default:
return false;
}
@@ -313,9 +324,9 @@ public class TunnelListFragment extends BaseFragment {
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
actionMode = mode;
if (getActivity() != null)
if (getActivity() != null) {
resources = getActivity().getResources();
tunnelList = binding.tunnelList;
}
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
return true;
}
@@ -324,12 +335,31 @@ public class TunnelListFragment extends BaseFragment {
public void onDestroyActionMode(final ActionMode mode) {
actionMode = null;
resources = null;
checkedItems.clear();
binding.tunnelList.getAdapter().notifyDataSetChanged();
}
@Override
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
final long id, final boolean checked) {
updateTitle(mode);
void toggleItemChecked(final int position) {
setItemChecked(position, !checkedItems.contains(position));
}
void setItemChecked(final int position, final boolean checked) {
if (checked) {
checkedItems.add(position);
} else {
checkedItems.remove(position);
}
if (actionMode == null && !checkedItems.isEmpty() && getActivity() != null) {
((AppCompatActivity) getActivity()).startSupportActionMode(this);
} else if (actionMode != null && checkedItems.isEmpty()) {
actionMode.finish();
}
binding.tunnelList.getAdapter().notifyItemChanged(position);
updateTitle(actionMode);
}
@Override
@@ -338,8 +368,12 @@ public class TunnelListFragment extends BaseFragment {
return false;
}
private void updateTitle(final ActionMode mode) {
final int count = (int) getCheckedPositions().count();
private void updateTitle(@Nullable final ActionMode mode) {
if (mode == null) {
return;
}
final int count = checkedItems.size();
if (count == 0) {
mode.setTitle("");
} else {
@@ -348,30 +382,4 @@ public class TunnelListFragment extends BaseFragment {
}
}
private final class ListViewCallbacks
implements OnItemClickListener, OnItemLongClickListener, OnTouchListener {
@Override
public void onItemClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
setSelectedTunnel((Tunnel) parent.getItemAtPosition(position));
}
@Override
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
if (actionMode != null)
return false;
if (binding != null)
binding.tunnelList.setItemChecked(position, true);
return true;
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(final View view, final MotionEvent motionEvent) {
if (binding != null)
binding.createMenu.collapse();
return false;
}
}
}
@@ -0,0 +1,60 @@
/*
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import com.wireguard.android.BR;
import com.wireguard.util.Keyed;
public class ApplicationData extends BaseObservable implements Keyed<String> {
@NonNull private final Drawable icon;
@NonNull private final String name;
@NonNull private final String packageName;
private boolean excludedFromTunnel;
public ApplicationData(@NonNull final Drawable icon, @NonNull final String name, @NonNull final String packageName, final boolean excludedFromTunnel) {
this.icon = icon;
this.name = name;
this.packageName = packageName;
this.excludedFromTunnel = excludedFromTunnel;
}
@NonNull
public Drawable getIcon() {
return icon;
}
@NonNull
public String getName() {
return name;
}
@NonNull
public String getPackageName() {
return packageName;
}
@Bindable
public boolean isExcludedFromTunnel() {
return excludedFromTunnel;
}
public void setExcludedFromTunnel(final boolean excludedFromTunnel) {
this.excludedFromTunnel = excludedFromTunnel;
notifyPropertyChanged(BR.excludedFromTunnel);
}
@Override
public String getKey() {
return name;
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model;
@@ -80,21 +80,21 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
}
public CompletionStage<State> getStateAsync() {
return manager.getTunnelState(this);
return TunnelManager.getTunnelState(this);
}
@Bindable
public Statistics getStatistics() {
// FIXME: Check age of statistics.
if (statistics == null)
manager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
TunnelManager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
return statistics;
}
public CompletionStage<Statistics> getStatisticsAsync() {
// FIXME: Check age of statistics.
if (statistics == null)
return manager.getTunnelStatistics(this);
return TunnelManager.getTunnelStatistics(this);
return CompletableFuture.completedFuture(statistics);
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model;
@@ -112,12 +112,12 @@ public final class TunnelManager extends BaseObservable {
.thenApply(tunnel::onConfigChanged);
}
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
static CompletionStage<State> getTunnelState(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel))
.thenApply(tunnel::onStateChanged);
}
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel))
.thenApply(tunnel::onStatisticsChanged);
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference;
@@ -1,10 +1,11 @@
/*
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -46,7 +47,9 @@ public class VersionPreference extends Preference {
protected void onClick() {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.wireguard.com/"));
getContext().startActivity(intent);
try {
getContext().startActivity(intent);
} catch (final ActivityNotFoundException ignored) { }
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Harsh Shandilya <msfjarvis@gmail.com>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget;
@@ -0,0 +1,238 @@
/*
* Copyright © 2018 The Android Open Source Project
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.FloatProperty;
@TargetApi(Build.VERSION_CODES.N)
public class SlashDrawable extends Drawable {
private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
private static final long QS_ANIM_LENGTH = 350;
private final Path mPath = new Path();
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// These values are derived in un-rotated (vertical) orientation
private static final float SLASH_WIDTH = 1.8384776f;
private static final float SLASH_HEIGHT = 28f;
private static final float CENTER_X = 10.65f;
private static final float CENTER_Y = 11.869239f;
private static final float SCALE = 24f;
// Bottom is derived during animation
private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
// Draw the slash washington-monument style; rotate to no-u-turn style
private static final float DEFAULT_ROTATION = -45f;
private Drawable mDrawable;
private final RectF mSlashRect = new RectF(0, 0, 0, 0);
private float mRotation;
private boolean mSlashed;
private Mode mTintMode;
private ColorStateList mTintList;
private boolean mAnimationEnabled = true;
public SlashDrawable(final Drawable d) {
setDrawable(d);
}
@Override
public int getIntrinsicHeight() {
return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
}
@Override
public int getIntrinsicWidth() {
return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
}
@Override
protected void onBoundsChange(final Rect bounds) {
super.onBoundsChange(bounds);
mDrawable.setBounds(bounds);
}
public void setDrawable(final Drawable d) {
mDrawable = d;
mDrawable.setCallback(getCallback());
mDrawable.setBounds(getBounds());
if (mTintMode != null)
mDrawable.setTintMode(mTintMode);
if (mTintList != null)
mDrawable.setTintList(mTintList);
invalidateSelf();
}
public void setRotation(final float rotation) {
if (mRotation == rotation)
return;
mRotation = rotation;
invalidateSelf();
}
public void setAnimationEnabled(final boolean enabled) {
mAnimationEnabled = enabled;
}
// Animate this value on change
private float mCurrentSlashLength;
private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
@Override
public void setValue(final SlashDrawable object, final float value) {
object.mCurrentSlashLength = value;
}
@Override
public Float get(final SlashDrawable object) {
return object.mCurrentSlashLength;
}
};
@SuppressWarnings("unchecked")
public void setSlashed(final boolean slashed) {
if (mSlashed == slashed) return;
mSlashed = slashed;
final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
if (mAnimationEnabled) {
final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
anim.setDuration(QS_ANIM_LENGTH);
anim.start();
} else {
mCurrentSlashLength = end;
invalidateSelf();
}
}
@SuppressWarnings("deprecation")
@Override
public void draw(@NonNull final Canvas canvas) {
canvas.save();
final Matrix m = new Matrix();
final int width = getBounds().width();
final int height = getBounds().height();
final float radiusX = scale(CORNER_RADIUS, width);
final float radiusY = scale(CORNER_RADIUS, height);
updateRect(
scale(LEFT, width),
scale(TOP, height),
scale(RIGHT, width),
scale(TOP + mCurrentSlashLength, height)
);
mPath.reset();
// Draw the slash vertically
mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
// Rotate -45 + desired rotation
m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
mPath.transform(m);
canvas.drawPath(mPath, mPaint);
// Rotate back to vertical
m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
mPath.transform(m);
// Draw another rect right next to the first, for clipping
m.setTranslate(mSlashRect.width(), 0);
mPath.transform(m);
mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
mPath.transform(m);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
canvas.clipPath(mPath, Region.Op.DIFFERENCE);
else
canvas.clipOutPath(mPath);
mDrawable.draw(canvas);
canvas.restore();
}
private float scale(final float frac, final int width) {
return frac * width;
}
private void updateRect(final float left, final float top, final float right, final float bottom) {
mSlashRect.left = left;
mSlashRect.top = top;
mSlashRect.right = right;
mSlashRect.bottom = bottom;
}
@Override
public void setTint(@ColorInt final int tintColor) {
super.setTint(tintColor);
mDrawable.setTint(tintColor);
mPaint.setColor(tintColor);
}
@Override
public void setTintList(@Nullable final ColorStateList tint) {
mTintList = tint;
super.setTintList(tint);
setDrawableTintList(tint);
mPaint.setColor(tint == null ? 0 : tint.getDefaultColor());
invalidateSelf();
}
private void setDrawableTintList(@Nullable final ColorStateList tint) {
mDrawable.setTintList(tint);
}
@Override
public void setTintMode(@NonNull final Mode tintMode) {
mTintMode = tintMode;
super.setTintMode(tintMode);
mDrawable.setTintMode(tintMode);
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
mDrawable.setAlpha(alpha);
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
mDrawable.setColorFilter(colorFilter);
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
@@ -1,97 +0,0 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import com.wireguard.android.R;
public class AddFloatingActionButton extends FloatingActionButton {
int mPlusColor;
public AddFloatingActionButton(final Context context) {
this(context, null);
}
public AddFloatingActionButton(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public AddFloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
void init(final Context context, final AttributeSet attributeSet) {
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0, 0);
mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_fab_plusIconColor, FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white));
attr.recycle();
super.init(context, attributeSet);
}
/**
* @return the current Color of plus icon.
*/
public int getPlusColor() {
return mPlusColor;
}
public void setPlusColor(final int color) {
if (mPlusColor != color) {
mPlusColor = color;
updateBackground();
}
}
public void setPlusColorResId(@ColorRes final int plusColor) {
setPlusColor(ContextCompat.getColor(getContext(), plusColor));
}
@Override
public void setIcon(@DrawableRes final int icon) {
throw new UnsupportedOperationException("Use FloatingActionButton if you want to use custom icon");
}
@Override
Drawable getIconDrawable() {
final float iconSize = getDimension(R.dimen.fab_icon_size);
final float iconHalfSize = iconSize / 2f;
final float plusSize = getDimension(R.dimen.fab_plus_icon_size);
final float plusHalfStroke = getDimension(R.dimen.fab_plus_icon_stroke) / 2f;
final float plusOffset = (iconSize - plusSize) / 2f;
final Shape shape = new Shape() {
@Override
public void draw(final Canvas canvas, final Paint paint) {
canvas.drawRect(plusOffset, iconHalfSize - plusHalfStroke, iconSize - plusOffset, iconHalfSize + plusHalfStroke, paint);
canvas.drawRect(iconHalfSize - plusHalfStroke, plusOffset, iconHalfSize + plusHalfStroke, iconSize - plusOffset, paint);
}
};
final ShapeDrawable drawable = new ShapeDrawable(shape);
final Paint paint = drawable.getPaint();
paint.setColor(mPlusColor);
paint.setStyle(Style.FILL);
paint.setAntiAlias(true);
return drawable;
}
}
@@ -1,446 +0,0 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable.ShaderFactory;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatImageButton;
import android.util.AttributeSet;
import android.widget.TextView;
import com.wireguard.android.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class FloatingActionButton extends AppCompatImageButton {
public static final int SIZE_NORMAL = 0;
public static final int SIZE_MINI = 1;
int mColorNormal;
int mColorPressed;
int mColorDisabled;
String mTitle;
boolean mStrokeVisible;
@DrawableRes
private int mIcon;
private Drawable mIconDrawable;
private int mSize;
private float mCircleSize;
private float mShadowRadius;
private float mShadowOffset;
private int mDrawableSize;
public FloatingActionButton(final Context context) {
this(context, null);
}
public FloatingActionButton(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public static int getColorFromTheme(final Context context, final int themeResource, @ColorRes final int fallback) {
final TypedArray a = context.obtainStyledAttributes(new int[]{themeResource});
try {
return a.getColor(0, ContextCompat.getColor(context, fallback));
} finally {
a.recycle();
}
}
void init(final Context context, final AttributeSet attributeSet) {
final TypedArray attr = context.obtainStyledAttributes(attributeSet,
R.styleable.FloatingActionButton, 0, 0);
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal,
getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed,
darkenOrLightenColor(mColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled,
ContextCompat.getColor(context, android.R.color.darker_gray)); //TODO(msf): load from theme
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true);
attr.recycle();
updateCircleSize();
mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
updateDrawableSize();
updateBackground();
}
private void updateDrawableSize() {
mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);
}
private void updateCircleSize() {
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
}
@FAB_SIZE
public int getSize() {
return mSize;
}
public void setSize(@FAB_SIZE final int size) {
if (size != SIZE_MINI && size != SIZE_NORMAL) {
throw new IllegalArgumentException("Use @FAB_SIZE constants only!");
}
if (mSize != size) {
mSize = size;
updateCircleSize();
updateDrawableSize();
updateBackground();
}
}
public void setIcon(@DrawableRes final int icon) {
if (mIcon != icon) {
mIcon = icon;
mIconDrawable = null;
updateBackground();
}
}
/**
* @return the current Color for normal state.
*/
public int getColorNormal() {
return mColorNormal;
}
public void setColorNormal(final int color) {
if (mColorNormal != color) {
mColorNormal = color;
updateBackground();
}
}
public void setColorNormalResId(@ColorRes final int colorNormal) {
setColorNormal(ContextCompat.getColor(getContext(), colorNormal));
}
/**
* @return the current color for pressed state.
*/
public int getColorPressed() {
return mColorPressed;
}
public void setColorPressed(final int color) {
if (mColorPressed != color) {
mColorPressed = color;
updateBackground();
}
}
public void setColorPressedResId(@ColorRes final int colorPressed) {
setColorPressed(ContextCompat.getColor(getContext(), colorPressed));
}
/**
* @return the current color for disabled state.
*/
public int getColorDisabled() {
return mColorDisabled;
}
public void setColorDisabled(final int color) {
if (mColorDisabled != color) {
mColorDisabled = color;
updateBackground();
}
}
public void setColorDisabledResId(@ColorRes final int colorDisabled) {
setColorDisabled(ContextCompat.getColor(getContext(), colorDisabled));
}
public boolean isStrokeVisible() {
return mStrokeVisible;
}
public void setStrokeVisible(final boolean visible) {
if (mStrokeVisible != visible) {
mStrokeVisible = visible;
updateBackground();
}
}
float getDimension(@DimenRes final int id) {
return getResources().getDimension(id);
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle() {
return mTitle;
}
public void setTitle(final String title) {
mTitle = title;
final TextView label = getLabelView();
if (label != null) {
label.setText(title);
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDrawableSize, mDrawableSize);
}
void updateBackground() {
final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
final float halfStrokeWidth = strokeWidth / 2f;
final LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{
//TODO(msf); replace these pngs with programatic elevation
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini, null),
createFillDrawable(strokeWidth),
createOuterStrokeDrawable(strokeWidth),
getIconDrawable()
});
final int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2;
final int circleInsetHorizontal = (int) (mShadowRadius);
final int circleInsetTop = (int) (mShadowRadius - mShadowOffset);
final int circleInsetBottom = (int) (mShadowRadius + mShadowOffset);
layerDrawable.setLayerInset(1,
circleInsetHorizontal,
circleInsetTop,
circleInsetHorizontal,
circleInsetBottom);
layerDrawable.setLayerInset(2,
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetTop - halfStrokeWidth),
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetBottom - halfStrokeWidth));
layerDrawable.setLayerInset(3,
circleInsetHorizontal + iconOffset,
circleInsetTop + iconOffset,
circleInsetHorizontal + iconOffset,
circleInsetBottom + iconOffset);
setBackground(layerDrawable);
}
Drawable getIconDrawable() {
if (mIconDrawable != null) {
return mIconDrawable;
} else if (mIcon != 0) {
return ContextCompat.getDrawable(getContext(), mIcon);
} else {
return new ColorDrawable(Color.TRANSPARENT);
}
}
public void setIconDrawable(@NonNull final Drawable iconDrawable) {
if (mIconDrawable != iconDrawable) {
mIcon = 0;
mIconDrawable = iconDrawable;
updateBackground();
}
}
private StateListDrawable createFillDrawable(final float strokeWidth) {
final StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled, strokeWidth));
drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed, strokeWidth));
drawable.addState(new int[]{}, createCircleDrawable(mColorNormal, strokeWidth));
return drawable;
}
private Drawable createCircleDrawable(final int color, final float strokeWidth) {
final int alpha = Color.alpha(color);
final int opaqueColor = opaque(color);
final ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = fillDrawable.getPaint();
paint.setAntiAlias(true);
paint.setColor(opaqueColor);
final Drawable[] layers = {
fillDrawable,
createInnerStrokesDrawable(opaqueColor, strokeWidth)
};
final LayerDrawable drawable = alpha == 255 || !mStrokeVisible
? new LayerDrawable(layers)
: new TranslucentLayerDrawable(alpha, layers);
final int halfStrokeWidth = (int) (strokeWidth / 2f);
drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
return drawable;
}
private static Drawable createOuterStrokeDrawable(final float strokeWidth) {
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAlpha(opacityToAlpha(0.02f));
return shapeDrawable;
}
private static int opacityToAlpha(final float opacity) {
return (int) (255f * opacity);
}
private static int darkenColor(final int argb) {
return adjustColorBrightness(argb, 0.9f);
}
private static int lightenColor(final int argb) {
return adjustColorBrightness(argb, 1.1f);
}
public static int darkenOrLightenColor(final int argb) {
final float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
final float factor;
if (hsv[2] < 0.2)
factor = 1.2f;
else
factor = 0.8f;
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private static int adjustColorBrightness(final int argb, final float factor) {
final float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private static int halfTransparent(final int argb) {
return Color.argb(
Color.alpha(argb) / 2,
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private static int opaque(final int argb) {
return Color.rgb(
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private Drawable createInnerStrokesDrawable(final int color, final float strokeWidth) {
if (!mStrokeVisible) {
return new ColorDrawable(Color.TRANSPARENT);
}
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final int bottomStrokeColor = darkenColor(color);
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
final int topStrokeColor = lightenColor(color);
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
shapeDrawable.setShaderFactory(new ShaderFactory() {
@Override
public Shader resize(int width, int height) {
return new LinearGradient(width / 2, 0, width / 2, height,
new int[]{topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor},
new float[]{0f, 0.2f, 0.5f, 0.8f, 1f},
TileMode.CLAMP
);
}
});
return shapeDrawable;
}
@Override
public void setVisibility(final int visibility) {
final TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_NORMAL, SIZE_MINI})
public @interface FAB_SIZE {
}
private static final class TranslucentLayerDrawable extends LayerDrawable {
private final int mAlpha;
private TranslucentLayerDrawable(final int alpha, final Drawable... layers) {
super(layers);
mAlpha = alpha;
}
@Override
public void draw(final Canvas canvas) {
final Rect bounds = getBounds();
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha);
super.draw(canvas);
canvas.restore();
}
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
@@ -21,6 +21,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
@@ -33,7 +35,6 @@ import android.widget.TextView;
import com.wireguard.android.R;
@SuppressWarnings("ReturnOfInnerClass")
public class FloatingActionsMenu extends ViewGroup {
public static final int EXPAND_UP = 0;
public static final int EXPAND_DOWN = 1;
@@ -49,11 +50,6 @@ public class FloatingActionsMenu extends ViewGroup {
private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator();
private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f);
private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator();
private int mAddButtonPlusColor;
private int mAddButtonColorNormal;
private int mAddButtonColorPressed;
private int mAddButtonSize;
private boolean mAddButtonStrokeVisible;
private int mExpandDirection;
private int mButtonSpacing;
private int mLabelsMargin;
@@ -61,7 +57,7 @@ public class FloatingActionsMenu extends ViewGroup {
private boolean mExpanded;
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private AddFloatingActionButton mAddButton;
private FloatingActionButton mAddButton;
private RotatingDrawable mRotatingDrawable;
private int mMaxButtonWidth;
private int mMaxButtonHeight;
@@ -87,7 +83,7 @@ public class FloatingActionsMenu extends ViewGroup {
}
private void init(final Context context, final AttributeSet attributeSet) {
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset));
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing));
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
@@ -95,14 +91,6 @@ public class FloatingActionsMenu extends ViewGroup {
setTouchDelegate(mTouchDelegateGroup);
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor,
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white));
mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal,
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright));
mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed,
FloatingActionButton.darkenOrLightenColor(mAddButtonColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states
mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL);
mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true);
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
@@ -124,45 +112,30 @@ public class FloatingActionsMenu extends ViewGroup {
}
private void createAddButton(final Context context) {
mAddButton = new AddFloatingActionButton(context) {
@Override
void updateBackground() {
mPlusColor = mAddButtonPlusColor;
mColorNormal = mAddButtonColorNormal;
mColorPressed = mAddButtonColorPressed;
mStrokeVisible = mAddButtonStrokeVisible;
super.updateBackground();
}
final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_inverse, context.getTheme()));
mRotatingDrawable = rotatingDrawable;
@Override
Drawable getIconDrawable() {
final RotatingDrawable rotatingDrawable = new RotatingDrawable(super.getIconDrawable());
mRotatingDrawable = rotatingDrawable;
final TimeInterpolator interpolator = new OvershootInterpolator();
final TimeInterpolator interpolator = new OvershootInterpolator();
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
collapseAnimator.setInterpolator(interpolator);
expandAnimator.setInterpolator(interpolator);
collapseAnimator.setInterpolator(interpolator);
expandAnimator.setInterpolator(interpolator);
mExpandAnimation.play(expandAnimator);
mCollapseAnimation.play(collapseAnimator);
return rotatingDrawable;
}
};
mExpandAnimation.play(expandAnimator);
mCollapseAnimation.play(collapseAnimator);
mAddButton = new FloatingActionButton(context);
mAddButton.setImageDrawable(rotatingDrawable);
mAddButton.setId(R.id.fab_expand_menu_button);
mAddButton.setSize(mAddButtonSize);
mAddButton.setOnClickListener(v -> toggle());
addView(mAddButton, super.generateDefaultLayoutParams());
mButtonsCount++;
}
public void addButton(final FloatingActionButton button) {
public void addButton(final LabeledFloatingActionButton button) {
addView(button, mButtonsCount - 1);
mButtonsCount++;
@@ -171,7 +144,7 @@ public class FloatingActionsMenu extends ViewGroup {
}
}
public void removeButton(final FloatingActionButton button) {
public void removeButton(final LabeledFloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null);
@@ -256,9 +229,9 @@ public class FloatingActionsMenu extends ViewGroup {
final int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0;
// Ensure mAddButton is centered on the line where the buttons should be
final int buttonsHorizontalCenter = mLabelsPosition == LABELS_ON_LEFT_SIDE
final int buttonsHorizontalCenter = (mLabelsPosition == LABELS_ON_LEFT_SIDE
? r - l - mMaxButtonWidth / 2
: mMaxButtonWidth / 2;
: mMaxButtonWidth / 2);
final int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2;
mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight());
@@ -313,7 +286,7 @@ public class FloatingActionsMenu extends ViewGroup {
childY - mButtonSpacing / 2,
Math.max(childX + child.getMeasuredWidth(), labelRight),
childY + child.getMeasuredHeight() + mButtonSpacing / 2);
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(touchArea, child));
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(new Rect(touchArea), child));
label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
label.setAlpha(mExpanded ? 1f : 0f);
@@ -404,17 +377,17 @@ public class FloatingActionsMenu extends ViewGroup {
for (int i = 0; i < mButtonsCount; i++) {
final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
final String title = button.getTitle();
if (button == mAddButton || title == null ||
button.getTag(R.id.fab_label) != null) continue;
if (button instanceof LabeledFloatingActionButton) {
final String title = ((LabeledFloatingActionButton) button).getTitle();
final AppCompatTextView label = new AppCompatTextView(context);
label.setTextAppearance(context, mLabelsStyle);
label.setText(button.getTitle());
addView(label);
final AppCompatTextView label = new AppCompatTextView(context);
label.setTextAppearance(context, mLabelsStyle);
label.setText(title);
addView(label);
button.setTag(R.id.fab_label, label);
button.setTag(R.id.fab_label, label);
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.widget.TextView;
import com.wireguard.android.R;
public class LabeledFloatingActionButton extends FloatingActionButton {
private final String title;
public LabeledFloatingActionButton(final Context context) {
this(context, null);
}
public LabeledFloatingActionButton(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public LabeledFloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.LabeledFloatingActionButton, 0, 0);
title = attr.getString(R.styleable.LabeledFloatingActionButton_fab_title);
attr.recycle();
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle() {
return title;
}
@Override
public void setVisibility(final int visibility) {
final TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
@@ -43,13 +43,14 @@ public class TouchDelegateGroup extends TouchDelegate {
@Override
public boolean onTouchEvent(@NonNull final MotionEvent event) {
if (!mEnabled) return false;
if (!mEnabled)
return false;
TouchDelegate delegate = null;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (final TouchDelegate touchDelegate : mTouchDelegates) {
for (final TouchDelegate touchDelegate : mTouchDelegates) {
if (touchDelegate.onTouchEvent(event)) {
mCurrentTouchDelegate = touchDelegate;
return true;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -18,10 +18,11 @@ import java.util.regex.Pattern;
* The set of valid attributes for an interface or peer in a WireGuard configuration file.
*/
enum Attribute {
public enum Attribute {
ADDRESS("Address"),
ALLOWED_IPS("AllowedIPs"),
DNS("DNS"),
EXCLUDED_APPLICATIONS("ExcludedApplications"),
ENDPOINT("Endpoint"),
LISTEN_PORT("ListenPort"),
MTU("MTU"),
@@ -59,7 +60,7 @@ enum Attribute {
}
public static String[] stringToList(final String string) {
if (string == null)
if (TextUtils.isEmpty(string))
return EMPTY_LIST;
return LIST_SEPARATOR_PATTERN.split(string.trim());
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -16,6 +16,7 @@ import com.wireguard.crypto.Keypair;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -25,6 +26,7 @@ import java.util.List;
public class Interface {
private final List<InetNetwork> addressList;
private final List<InetAddress> dnsList;
private final List<String> excludedApplications;
private Keypair keypair;
private int listenPort;
private int mtu;
@@ -32,6 +34,7 @@ public class Interface {
public Interface() {
addressList = new ArrayList<>();
dnsList = new ArrayList<>();
excludedApplications = new ArrayList<>();
}
private void addAddresses(final String[] addresses) {
@@ -52,6 +55,12 @@ public class Interface {
}
}
private void addExcludedApplications(final String[] applications) {
if (applications != null && applications.length > 0) {
excludedApplications.addAll(Arrays.asList(applications));
}
}
private String getAddressString() {
if (addressList.isEmpty())
return null;
@@ -79,6 +88,16 @@ public class Interface {
return dnsList.toArray(new InetAddress[dnsList.size()]);
}
private String getExcludedApplicationsString() {
if (excludedApplications.isEmpty())
return null;
return Attribute.iterableToString(excludedApplications);
}
public String[] getExcludedApplications() {
return excludedApplications.toArray(new String[excludedApplications.size()]);
}
public int getListenPort() {
return listenPort;
}
@@ -120,6 +139,9 @@ public class Interface {
case DNS:
addDnses(key.parseList(line));
break;
case EXCLUDED_APPLICATIONS:
addExcludedApplications(key.parseList(line));
break;
case LISTEN_PORT:
setListenPortString(key.parse(line));
break;
@@ -144,6 +166,11 @@ public class Interface {
addDnses(Attribute.stringToList(dnsString));
}
private void setExcludedApplicationsString(final String applicationsString) {
excludedApplications.clear();
addExcludedApplications(Attribute.stringToList(applicationsString));
}
private void setListenPort(final int listenPort) {
this.listenPort = listenPort;
}
@@ -179,6 +206,8 @@ public class Interface {
sb.append(Attribute.ADDRESS.composeWith(addressList));
if (!dnsList.isEmpty())
sb.append(Attribute.DNS.composeWith(getDnsStrings()));
if (!excludedApplications.isEmpty())
sb.append(Attribute.EXCLUDED_APPLICATIONS.composeWith(excludedApplications));
if (listenPort != 0)
sb.append(Attribute.LISTEN_PORT.composeWith(listenPort));
if (mtu != 0)
@@ -202,6 +231,7 @@ public class Interface {
};
private String addresses;
private String dnses;
private String excludedApplications;
private String listenPort;
private String mtu;
private String privateKey;
@@ -219,11 +249,13 @@ public class Interface {
privateKey = in.readString();
listenPort = in.readString();
mtu = in.readString();
excludedApplications = in.readString();
}
public void commitData(final Interface parent) {
parent.setAddressString(addresses);
parent.setDnsString(dnses);
parent.setExcludedApplicationsString(excludedApplications);
parent.setPrivateKey(privateKey);
parent.setListenPortString(listenPort);
parent.setMtuString(mtu);
@@ -254,6 +286,16 @@ public class Interface {
return dnses;
}
@Bindable
public String getExcludedApplications() {
return excludedApplications;
}
@Bindable
public int getExcludedApplicationsCount() {
return Attribute.stringToList(excludedApplications).length;
}
@Bindable
public String getListenPort() {
return listenPort;
@@ -277,6 +319,7 @@ public class Interface {
protected void loadData(final Interface parent) {
addresses = parent.getAddressString();
dnses = parent.getDnsString();
excludedApplications = parent.getExcludedApplicationsString();
publicKey = parent.getPublicKey();
privateKey = parent.getPrivateKey();
listenPort = parent.getListenPortString();
@@ -293,6 +336,12 @@ public class Interface {
notifyPropertyChanged(BR.dnses);
}
public void setExcludedApplications(final String excludedApplications) {
this.excludedApplications = excludedApplications;
notifyPropertyChanged(BR.excludedApplications);
notifyPropertyChanged(BR.excludedApplicationsCount);
}
public void setListenPort(final String listenPort) {
this.listenPort = listenPort;
notifyPropertyChanged(BR.listenPort);
@@ -324,6 +373,7 @@ public class Interface {
dest.writeString(privateKey);
dest.writeString(listenPort);
dest.writeString(mtu);
dest.writeString(excludedApplications);
}
}
}
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
@@ -20,9 +20,14 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java9.lang.Iterables;
/**
* Represents the configuration for a WireGuard peer (a [Peer] block).
*/
@@ -211,6 +216,8 @@ public class Peer {
private String persistentKeepalive;
private String preSharedKey;
private String publicKey;
private List<String> interfaceDNSRoutes;
private int numSiblings;
public Observable(final Peer parent) {
loadData(parent);
@@ -222,6 +229,9 @@ public class Peer {
persistentKeepalive = in.readString();
preSharedKey = in.readString();
publicKey = in.readString();
numSiblings = in.readInt();
interfaceDNSRoutes = new ArrayList<>();
in.readStringList(interfaceDNSRoutes);
}
public static Observable newInstance() {
@@ -245,6 +255,35 @@ public class Peer {
return 0;
}
private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0";
private static final List<String> DEFAULT_ROUTE_MOD_RFC1918_V4 = Arrays.asList("0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
public void toggleExcludePrivateIPs() {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean hasDefaultRoute = ips.contains(DEFAULT_ROUTE_V4);
final boolean hasDefaultRouteModRFC1918 = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0)
return;
Iterables.removeIf(ips, ip -> !ip.contains(":"));
if (hasDefaultRoute) {
ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.addAll(interfaceDNSRoutes);
} else if (hasDefaultRouteModRFC1918)
ips.add(DEFAULT_ROUTE_V4);
setAllowedIPs(Attribute.iterableToString(ips));
}
@Bindable
public boolean getCanToggleExcludePrivateIPs() {
final Collection<String> ips = Arrays.asList(Attribute.stringToList(allowedIPs));
return numSiblings == 0 && (ips.contains(DEFAULT_ROUTE_V4) || ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4));
}
@Bindable
public boolean getIsExcludePrivateIPsOn() {
return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
}
@Bindable
public String getAllowedIPs() {
return allowedIPs;
@@ -276,11 +315,14 @@ public class Peer {
persistentKeepalive = parent.getPersistentKeepaliveString();
preSharedKey = parent.getPreSharedKey();
publicKey = parent.getPublicKey();
interfaceDNSRoutes = new ArrayList<>();
}
public void setAllowedIPs(final String allowedIPs) {
this.allowedIPs = allowedIPs;
notifyPropertyChanged(BR.allowedIPs);
notifyPropertyChanged(BR.canToggleExcludePrivateIPs);
notifyPropertyChanged(BR.isExcludePrivateIPsOn);
}
public void setEndpoint(final String endpoint) {
@@ -303,6 +345,27 @@ public class Peer {
notifyPropertyChanged(BR.publicKey);
}
public void setInterfaceDNSRoutes(final String dnsServers) {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.removeAll(interfaceDNSRoutes);
interfaceDNSRoutes.clear();
for (final String dnsServer : Attribute.stringToList(dnsServers)) {
if (!dnsServer.contains(":"))
interfaceDNSRoutes.add(dnsServer + "/32");
}
ips.addAll(interfaceDNSRoutes);
if (modifyAllowedIPs)
setAllowedIPs(Attribute.iterableToString(ips));
}
public void setNumSiblings(final int num) {
numSiblings = num;
notifyPropertyChanged(BR.canToggleExcludePrivateIPs);
notifyPropertyChanged(BR.isExcludePrivateIPsOn);
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(allowedIPs);
@@ -310,6 +373,8 @@ public class Peer {
dest.writeString(persistentKeepalive);
dest.writeString(preSharedKey);
dest.writeString(publicKey);
dest.writeInt(numSiblings);
dest.writeStringList(interfaceDNSRoutes);
}
}
}
@@ -1,6 +1,6 @@
/*
* Copyright © 2016 Southern Storm Software, Pty Ltd.
* SPDX-License-Identifier: MIT
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
@@ -1,6 +1,6 @@
/*
* Copyright © 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
@@ -1,7 +1,7 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.util;
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.util;
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.util;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="?android:attr/colorBackground"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<path
android:fillColor="?android:attr/colorForeground"
android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" />
</vector>
@@ -1,29 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="400dp"
android:height="400dp"
android:viewportHeight="400.0"
android:viewportWidth="400.0">
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="M197.7,0C191.5,0.1 185.2,0.5 178.7,1.1C191.1,4 202.2,6.6 213.4,9.2C213.3,9.8 213.1,10.5 213,11.2C198.1,13.2 183.9,7.7 169.3,5.7C174.6,8.8 180,11.7 185.5,14.2C191.2,16.7 197,18.8 202.8,21.2C195.4,27.5 188,28.8 178.7,26.7C173.7,25.6 168.3,25 163.1,25.2C157.8,25.5 152.4,26.8 147.5,30C152.7,32.7 157.5,34.8 162.1,37.5C163.9,38.6 166.1,40.4 166.6,42.4C167.8,46.9 168.2,51.8 168.9,56.5C160.4,57.5 145.4,66.1 142.4,71.7C155.5,74.3 169.7,71.2 182.2,79.6C178.1,82.8 168.5,86.6 165,89.3C169.3,90.4 179.4,89.9 183.3,89.6C186.6,89.4 188.2,89.3 189.5,90.4L228,120.6C232.1,123.8 248.4,139.3 252.7,149C256.3,157.3 256.8,164.4 256.8,166.1C256.8,170.7 256.2,177.9 253,186C252.7,186.9 252,188.3 251.2,189.8L280.8,219.4C288.3,207.7 293.4,194.5 296.3,180C299.6,163.2 299.4,146.5 291.7,130.5C285.8,118.3 276.1,109.4 265.8,101.3C255.1,93 243.7,85.4 233.1,77C230.2,74.8 228.3,70.8 226.9,67.3C226.4,65.8 228.2,61.7 229.4,61.5C236,60.3 242.6,59.7 249.3,59.4C256.9,59.2 264.6,59.4 272.3,59.5C274,59.5 276.2,59.3 277.2,60.3C281.2,64.2 284.3,61.6 287,59.1C289.4,56.9 291,54 292.9,51.6C291.7,51.4 289.4,50.9 287.1,50.8C279.4,50.6 271.7,50.7 264,50.5C262.6,50.4 261.3,49 259.9,48.2C261.3,47.6 262.8,46.6 264.2,46.6C277.5,46.5 290.8,46.5 304.1,46.5C304.2,39.6 294.9,30.1 286.7,27.5C286.6,28.5 286.5,29.3 286.5,30.3C278.3,30.5 270.3,30.3 263,26.4C261.1,25.4 259.8,23.1 258.3,21.5C256.3,19.3 254.7,16.6 252.3,15.2C247.4,12.3 242,10.3 236.9,7.8C224.3,1.7 211.3,-0.1 197.7,0zM249.6,29.4C250.3,29.4 250.9,29.6 251.6,30.2C252.6,31 253.6,31.9 254.8,33C253.3,33.8 252,34.5 250.8,35.1C249,36 247.6,35.4 246.5,34C245.6,32.8 245.5,31.7 246.8,30.7C247.7,30 248.6,29.4 249.6,29.4zM231.8,213C220.7,221.9 207.6,227.2 193.6,230.1C152.7,238.5 118.7,282.2 128.3,330.3C139.5,386.5 201.5,416.9 252.2,390.2C285,372.9 302.3,339.2 297.7,302.5C295.9,288.1 291,275.4 283.1,264.3L265.1,246.3C264.2,246.2 263.2,246.6 261.9,247.5C253.1,253.1 244.2,258.4 235.1,263.4C229.9,266.2 224.2,268.3 217.7,271.2C219.9,271.8 221,272.1 222.1,272.4C246.5,278.9 259.6,300.3 253.8,324.4C248.7,345.7 227,359.4 205.9,355.8C188.4,352.8 173.1,338.2 170.5,320.8C167.8,301.8 177.2,283.5 194.1,275.9C203.4,271.6 213,267.9 222.3,263.6C232.9,258.7 244.4,254.9 253.6,248.1C256.2,246.2 258.7,244.2 261,242.2L231.8,213z"
android:strokeColor="#00000000"
android:strokeWidth="1.3750788" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="m97.9,307.6c-7.8,2 -15.4,4.9 -23.4,7.5 3.9,-26.3 34.7,-50.6 60.8,-47.8 -8.1,10.9 -11.8,23.3 -12.7,35.6 -8.7,1.6 -16.8,2.7 -24.8,4.7"
android:strokeColor="#00000000"
android:strokeWidth="1.3750788" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="M177,111.6C175.7,111.6 174.4,111.7 173.1,111.7L247,185.6C248.1,183.2 249,180.6 249.9,177.9C252.2,170.4 251.8,159.3 248,152.1C234.7,126.6 206.6,111.3 177,111.6zM139.7,120.9C137.9,121.8 136.1,122.9 134.3,124C81.2,156.4 84.3,231 132.9,260.7C136.6,263 138.3,262.7 140.8,259.4C148.4,249.2 157.9,241.1 169,234.9C171.7,233.3 174.5,231.9 178.1,230C154,225.8 141.1,214.8 139.6,197.8C137.9,179.4 146.9,164.5 163.2,158.3C167,156.9 171,156.2 174.9,156.1L139.7,120.9zM212.5,193.7C212.5,195.8 212.3,197.9 211.8,200.1C209.9,210.5 203.6,218.1 195.8,224.8C208.5,221.9 219.6,216.9 228.6,209.7L212.5,193.7z"
android:strokeColor="#00000000"
android:strokeWidth="1.3750788" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:pathData="M78.4,59.6l262,262l-21.3,21.3l-262,-262z"
android:strokeWidth="0.82064575" />
</vector>
@@ -2,4 +2,5 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/list_item_ripple"> <!-- TODO(msf): themeify this -->
<item android:drawable="@drawable/list_item_background" />
<item android:id="@android:id/mask" android:drawable="@android:color/white" />
</ripple>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<import type="com.wireguard.android.model.ApplicationData" />
<variable
name="fragment"
type="com.wireguard.android.fragment.AppListDialogFragment" />
<variable
name="appData"
type="com.wireguard.android.util.ObservableKeyedList&lt;String, ApplicationData&gt;" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="200dp" >
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="@{appData.isEmpty() ? View.VISIBLE : View.GONE}"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/app_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{appData}"
app:layout="@{@layout/app_list_item}" />
</FrameLayout>
</layout>
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.wireguard.android.model.ApplicationData" />
<variable
name="collection"
type="com.wireguard.android.util.ObservableKeyedList&lt;String, com.wireguard.android.model.ApplicationData&gt;" />
<variable
name="key"
type="String" />
<variable
name="item"
type="com.wireguard.android.model.ApplicationData" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_background_anim"
android:padding="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}">
<ImageView
android:id="@+id/app_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@{item.icon}" />
<TextView
android:id="@+id/app_name"
style="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:text="@{key}" />
<CheckBox
android:id="@+id/excluded_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={item.excludedFromTunnel}" />
</LinearLayout>
</layout>
@@ -5,12 +5,14 @@
<data>
<import type="com.wireguard.android.fragment.TunnelController" />
<import type="com.wireguard.android.model.Tunnel.State" />
<import type="com.wireguard.android.util.ClipboardUtils" />
<variable
name="fragment"
type="com.wireguard.android.fragment.TunnelDetailFragment" />
<variable
name="tunnel"
type="com.wireguard.android.model.Tunnel" />
@@ -63,7 +65,7 @@
android:layout_alignBaseline="@+id/interface_title"
android:layout_alignParentEnd="true"
app:checked="@{tunnel.state == State.UP}"
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" />
app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
<TextView
android:id="@+id/interface_name_label"
@@ -13,6 +13,10 @@
<import type="com.wireguard.config.Peer" />
<variable
name="fragment"
type="com.wireguard.android.fragment.TunnelEditorFragment" />
<variable
name="config"
type="com.wireguard.config.Config.Observable" />
@@ -96,6 +100,7 @@
app:filter="@{KeyInputFilter.newInstance()}" />
<Button
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:id="@+id/generate_private_key_button"
android:layout_width="96dp"
android:layout_height="wrap_content"
@@ -210,6 +215,16 @@
android:inputType="number"
android:text="@={config.interfaceSection.mtu}"
android:textAlignment="center" />
<Button
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:id="@+id/set_excluded_applications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="-8dp"
android:layout_below="@+id/dns_servers_text"
android:onClick="@{fragment::onRequestSetExcludedApplications}"
android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" />
</RelativeLayout>
</android.support.v7.widget.CardView>
@@ -223,6 +238,7 @@
tools:ignore="UselessLeaf" />
<Button
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
@@ -4,6 +4,7 @@
<data>
<import type="android.view.View"/>
<import type="com.wireguard.android.widget.KeyInputFilter" />
<variable
@@ -92,9 +93,21 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/pre_shared_key_text"
android:layout_toStartOf="@+id/exclude_private_ips"
android:labelFor="@+id/allowed_ips_text"
android:text="@string/allowed_ips" />
<CheckBox
android:id="@+id/exclude_private_ips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/allowed_ips_label"
android:layout_alignParentEnd="true"
android:checked="@{item.isExcludePrivateIPsOn}"
android:onClick="@{() -> item.toggleExcludePrivateIPs()}"
android:text="@string/exclude_private_ips"
android:visibility="@{item.canToggleExcludePrivateIPs ? View.VISIBLE : View.GONE}" />
<EditText
android:id="@+id/allowed_ips_text"
android:layout_width="match_parent"
@@ -10,6 +10,10 @@
name="fragment"
type="com.wireguard.android.fragment.TunnelListFragment" />
<variable
name="rowConfigurationHandler"
type="com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler" />
<variable
name="tunnels"
type="com.wireguard.android.util.ObservableKeyedList&lt;String, Tunnel&gt;" />
@@ -19,42 +23,44 @@
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground">
android:background="?android:attr/colorBackground"
android:clipChildren="false">
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/tunnel_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="multipleChoiceModal"
app:items="@{tunnels}"
app:layout="@{@layout/tunnel_list_item}" />
app:layout="@{@layout/tunnel_list_item}"
app:configurationHandler="@{rowConfigurationHandler}" />
<com.wireguard.android.widget.fab.FloatingActionsMenu
android:id="@+id/create_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:layout_margin="16dp"
app:fab_labelStyle="@style/fab_label"
app:fab_labelsPosition="left"
app:layout_dodgeInsetEdges="bottom">
android:clipChildren="false"
app:fab_labelsPosition="left" >
<com.wireguard.android.widget.fab.FloatingActionButton
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/create_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestCreateConfig}"
app:fab_icon="@drawable/ic_action_edit_inverse"
app:fab_size="mini"
app:fabSize="mini"
app:srcCompat="@drawable/ic_action_edit_inverse"
app:fab_title="@string/create_empty" />
<com.wireguard.android.widget.fab.FloatingActionButton
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/create_from_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestImportConfig}"
app:fab_icon="@drawable/ic_action_open_inverse"
app:fab_size="mini"
app:srcCompat="@drawable/ic_action_open_inverse"
app:fabSize="mini"
app:fab_title="@string/create_from_file" />
</com.wireguard.android.widget.fab.FloatingActionsMenu>
</android.support.design.widget.CoordinatorLayout>
+5 -3
View File
@@ -4,8 +4,6 @@
<data>
<import type="com.wireguard.android.fragment.TunnelController" />
<import type="com.wireguard.android.model.Tunnel" />
<import type="com.wireguard.android.model.Tunnel.State" />
@@ -21,6 +19,10 @@
<variable
name="item"
type="com.wireguard.android.model.Tunnel" />
<variable
name="fragment"
type="com.wireguard.android.fragment.TunnelListFragment" />
</data>
<RelativeLayout
@@ -49,6 +51,6 @@
android:layout_alignBaseline="@+id/tunnel_name"
android:layout_alignParentEnd="true"
app:checked="@{item.state == State.UP}"
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" />
app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
</RelativeLayout>
</layout>
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_action_select_all"
android:alphabeticShortcut="s"
android:icon="@drawable/ic_action_select_all"
android:title="@string/select_all"
app:showAsAction="always" />
<item
android:id="@+id/menu_action_delete"
android:alphabeticShortcut="d"
+2 -30
View File
@@ -3,46 +3,18 @@
<item name="fab_expand_menu_button" type="id"/>
<item name="fab_label" type="id"/>
<dimen name="fab_size_normal">56dp</dimen>
<dimen name="fab_size_mini">40dp</dimen>
<dimen name="fab_icon_size">24dp</dimen>
<dimen name="fab_plus_icon_size">14dp</dimen>
<dimen name="fab_plus_icon_stroke">2dp</dimen>
<dimen name="fab_shadow_offset">3dp</dimen>
<dimen name="fab_shadow_radius">9dp</dimen>
<dimen name="fab_stroke_width">1dp</dimen>
<dimen name="fab_actions_spacing">16dp</dimen>
<dimen name="fab_actions_spacing">24dp</dimen>
<dimen name="fab_labels_margin">8dp</dimen>
<declare-styleable name="FloatingActionButton">
<attr name="fab_colorPressed" format="color"/>
<attr name="fab_colorDisabled" format="color"/>
<attr name="fab_colorNormal" format="color"/>
<attr name="fab_icon" format="reference"/>
<attr name="fab_size" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<declare-styleable name="LabeledFloatingActionButton">
<attr name="fab_title" format="string"/>
<attr name="fab_stroke_visible" format="boolean"/>
</declare-styleable>
<declare-styleable name="AddFloatingActionButton">
<attr name="fab_plusIconColor" format="color"/>
</declare-styleable>
<declare-styleable name="FloatingActionsMenu">
<attr name="fab_addButtonColorPressed" format="color"/>
<attr name="fab_addButtonColorNormal" format="color"/>
<attr name="fab_addButtonSize" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<attr name="fab_addButtonPlusIconColor" format="color"/>
<attr name="fab_addButtonStrokeVisible" format="boolean"/>
<attr name="fab_labelStyle" format="reference"/>
<attr name="fab_labelsPosition" format="enum">
<enum name="left" value="0"/>
+11
View File
@@ -24,6 +24,7 @@
<string name="addresses">Addresses</string>
<string name="allowed_ips">Allowed IPs</string>
<string name="app_name">WireGuard</string>
<string name="cancel">Cancel</string>
<string name="config_save_error">Unable to save configuration for “%s”: %s</string>
<string name="config_save_success">Successfully saved configuration for “%s”</string>
<string name="create_activity_title">Create WireGuard Tunnel</string>
@@ -33,12 +34,17 @@
<string name="dark_theme_summary_on">Currently using dark night theme</string>
<string name="dark_theme_summary_off">Currently using light day theme</string>
<string name="delete">Delete</string>
<string name="deselect_all">Deselect All</string>
<string name="select_all">Select all</string>
<string name="dns_servers">DNS servers</string>
<string name="edit">Edit</string>
<string name="endpoint">Endpoint</string>
<string name="error_fetching_apps">Error fetching apps list: %s</string>
<string name="error_down">Error bringing down tunnel: %s</string>
<string name="error_root">Please obtain root access and try again</string>
<string name="error_up">Error bringing up tunnel: %s</string>
<string name="excluded_applications">Excluded Applications</string>
<string name="exclude_private_ips">Exclude private IPs</string>
<string name="generate">Generate</string>
<string name="hint_automatic">(auto)</string>
<string name="hint_generated">(generated)</string>
@@ -65,6 +71,11 @@
<string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string>
<string name="restore_on_boot_title">Restore on boot</string>
<string name="save">Save</string>
<plurals name="set_excluded_applications">
<item quantity="one">%d Excluded Application</item>
<item quantity="other">%d Excluded Applications</item>
</plurals>
<string name="set_exclusions">Set Exclusions</string>
<string name="settings">Settings</string>
<string name="toggle_error">Error toggling WireGuard tunnel: %s</string>
<string name="tools_installer_already">wg and wg-quick are already installed</string>
+1 -1
View File
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-License-Identifier: Apache-2.0
#
# Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+1 -1
View File
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
# SPDX-License-Identifier: Apache-2.0
#
# Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
/* SPDX-License-Identifier: Apache-2.0
*
* Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
/* SPDX-License-Identifier: Apache-2.0
*
* Copyright © 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
/* SPDX-License-Identifier: Apache-2.0
*
* Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/