Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-20 14:05:12 +01:00
parent 6e9a0d01ce
commit 7ee9806fba
2415 changed files with 312708 additions and 2 deletions

950
CHANGELOG.md Normal file
View file

@ -0,0 +1,950 @@
# Changelog
## [6.1.4] - 2024-10-27
- Renew web server TLS certificate
- Update Android gradle plugin
Special thanks to masudscloud for its bug report.
## [6.1.3] - 2024-07-06
- Fix mongoose web server
- Update translations
Special thanks to mbangi for its bug report.
## [6.1.2] - 2024-06-17
- Fix VPN restart on network interface change
- Fix crash when update is instantly download
- Update mongoose web server
- Update third party libraries
- Update Android gradle plugin
- Update translations
Special thanks to @Fs00 and @gallegonovato for their bug reports.
## [6.1.1] - 2023-06-20
- Improve adaptive launcher icon
- Update AndroidX libraries
- Update third party libraries
- Update libsu
- Update mongoose web server
- Update Android gradle plugin
- Update build tools
- Update translations
Special thanks to AzusaHana for its contribution.
## [6.1.0] - 2023-03-26
- Add adaptive launcher icon
- Add POST_NOTIFCATIONS runtime permission support
- Improve settings UI
- Update target SDK to Android 13
- Update libsu
- Update mongoose web server
- Update AndroidX libraries
- Update third party libraries
- Update Android gradle plugin
- Update NDK
## [6.0.3] - 2022-07-25
- Improve hosts source cache to reduce bandwidth usage
- Improve GitHub API usage to reduce bandwidth usage
- Update AndroidX libraries
- Update translations
Special thanks to yoshimo for its bug report.
## [6.0.2] - 2022-06-16
*This version is a pre-release*
- Fix wizard ad-block method check
- Update translations
Special thanks to Dandu32, ipdev and zhmstar0310 for their bug reports.
## [6.0.1] - 2022-06-03
*This version is a pre-release*
- Improve screen rotation handling on home screen
- Update libsu
- Update mongoose web server
- Update Android gradle plugin
- Update third party libraries
- Update translations
Special thanks to kubalav and zgfg for their bug reports.
## [6.0.0] - 2022-05-18
*This version is a pre-release*
- Add a new VPN ad-blocker implementation
- Add initial DOH (DNS Over HTTPS) support
- Add VPN connection monitor, heartbeat and throttler to improve reliability
- Add long press action to copy hostname from user lists and DNS log to clipboard
- Fix VPN state on network connectivity change and lost
- Fix VPN restart when system kills it
- Fix wrong DNS read on VPN restart
- Fix VPN unwanted restart while paused
- Fix crash on application update unknown size
- Improve VPN user control reliability
- Update logging system
- Update libsu
- Update Android gradle plugin
- Update AndroidX libraries
- Update NDK
- Update third party libraries
## [5.12.1] - 2022-05-14
- Fix hosts file install on Android 13
Special thanks to AAGaming00 and KieronQuinn for their bug reports.
## [5.12.0] - 2022-02-28
- Implement key-value pairs backup service support
- Improve VPN application exclusion UI
- Update AndroidX libraries
- Update mongoose web server
- Update translations
- Update gradle
- Update build tools
## [5.11.0] - 2021-12-20
- Improve home screen with icon color and decoration
- Improve settings UI elements
- Improve search filter performance in hosts list
- Improve resource clean up after parsing hosts source
- Improve logs with Timber
- Improve button descriptions
- Update Android gradle plugin
- Update AndroidX libraries
- Update third party libraries
- Update mongoose web server
- Update translations
Special thanks to brijrajparmar27 for its contribution.
## [5.10.0] - 2021-12-01
- Fix connectivity change detection in VPN mode
- Update mongoose web server
- Update target SDK to Android 12
- Update dependencies
- Update translations
Special thanks to 8asuj6m2 for its contribution.
## [5.9.0] - 2021-11-05
- Improve update activity to add support links and open from notification
- Update web server certificate to comply with maximum validity time
- Update dependencies
- Update Android gradle plugin
- Update NDK
Special thanks to rany2 for its bug report.
## [5.8.0] - 2021-08-08
- Improve command receiver for task automation
- Update mongoose web server
- Update dependencies
- Update Android gradle plugin
- Update translations
Special thanks to Faedelity for its bug report.
## [5.7.0] - 2021-06-27
- Add quick settings tile to toggle ad-blocking
- Fix crash on TLS and timeout issue during source update
- Fix backup not listed for restoration on older devices
- Update dependencies
- Update translations
Special thanks to gwolf2u, opusforlife2, Vstory for their bug reports.
## [5.6.0] - 2021-04-30
- Improve navigation by moving DNS logs to home screen
- Improve DNS logs usage by explaining usage and limitation
- Update dependencies
- Update translations
## [5.5.1] - 2021-04-02
- Add redirection validation
- Improve application update screen
- Update Android gradle plugin
- Update NDK
- Update dependencies
- Fix VPN crash when the only system DNS server available uses IPv6 and IPv6 is disabled from settings
- Remove html-textview dependency and jcenter repository
Special thanks to FrostbiterTy, SapphireExile, zgfg for their bug reports.
## [5.5.0] - 2021-03-17
- Add allow list support
- Improve source edition UI
- Improve source update check
- Improve animations
- Update mongoose web server
- Update dependencies
- Fix web server TLS issue
Special thanks to gallegonovato, jawz101 and zgfg for their bug reports.
## [5.4.0] - 2021-02-28
- Add VPN monitor option to prevent disconnection
- Update source update status indicator
- Update Android gradle plugin
- Update Sentry DSN to support older TLS versions
- Update mongoose web server
- Update dependencies
- Fix welcome screen telemetry preference
- Fix VPN authorization check at startup
Special thanks to BearTM, elvissteinjr, ipdev99, patkarmandar, RobBeyer for their bug reports.
## [5.3.0] - 2021-01-17
- Add new unique source parser with parallel processing
- Add an option to disable app update check at startup
- Fix crash with source file when SAF permission is removed
- Fix metered status vpn on Android 11
Special thanks to andy356, fusionneur and sr1canskhsia for their bug reports.
## [5.2.1] - 2021-01-06
- Fix F-Droid store detection
Special thanks to TheLonelyGhost, bdtipsntricks, faraz-b bege10, Nathan-Nesbitt and gallegonovato for their bug reports.
## [5.2.0] - 2021-01-03
- Add beta channel opt-in preferences
- Update mongoose to latest stable version and rewrite web server
- Update source parser to prevent stack overflow
- Update dependencies
- Update build tools
- Update NDK
- Update translations
## [5.1.0] - 2020-11-12
- Add application update UI
- Add F-Droid apk support to built-in updater
- Add custom hosts file parser for big hosts file (1M+ entries, way slower but memory friendly)
- Fix VPN app exclusion on Android 11
- Update AndroidX and Sentry dependencies
- Update translations
Special thanks to spiou, drothenberger and gallegonovato for their bug reports.
## [5.0.10] - 2020-10-10
*This version is a pre-release*
- Fix online modification date for unavailable source
- Prevent missing url at source creation
- Prevent invalid source url to be restored
- Fix web server certificate install on Android 11
Special thanks to zgfg, ipdev99 and ingenium13 for their bug reports.
## [5.0.9] - 2020-09-13
*This version is a pre-release*
- Fix no hosts to block from 4.x to 5.x migration
- Fix wrong source type in source edition UI
- Update AndroidX dependencies
- Update mongoose web server
Special thanks to auanasgheps, lukjod, ridobe, sacrificialpawn for their bug reports.
## [5.0.8] - 2020-08-30
*This version is a pre-release*
- Fix source not updated on automatic update
- Fix user list not sync until source update
- Update hosts source creation to disable allowed hosts by default
- Update AndroidX dependencies
Special thanks to jeanrivera for its bug report.
## [5.0.7] - 2020-08-16
*This version is a pre-release*
- Add hosts sources Storage Access Framework support
- Add hosts sources label and host counter
- Improve host sources list and edition UI
- Improve allowed and redirected hosts settings by applying them per source
- Fix file based hosts sources not installed due to missing permission
- Update AndroidX dependencies
Special thanks to zgfg for its bug report.
## [5.0.6] - 2020-08-02
*This version is a pre-release*
- Fix crash on domain enable/disable action
- Fix source not applied if disabled and enabled back
- Update target SDK to Android 11
Special thanks to ipdev99 and zgfg for their bug reports.
## [5.0.5] - 2020-06-28
*This version is a pre-release*
- Improve hosts source server handling with future time
- Improve hosts update by skipping already up-to-date sources
- Fix hosts source disable action
- Fix hosts list apply notification from non-user changes
- Fix user excluded application settings
- Remove navigation bar color customization
- Update AndroidX dependencies and NDK version
Special thanks to CobalTitan, ipdev99, zgfg and sunmybun for their bug reports.
## [4.3.5] - 2020-06-27
- Fix project Fastlane description for F-Droid store
Special thanks to linsui and IzzySoft for their bug reports and Vankog for translations.
## [5.0.4] - 2020-05-24
*This version is a pre-release*
- Improve overall host list computation
- Add host redirected feature in VPN ad blocking
- Remove WRITE_EXTERNAL_STORAGE permission (use Storage Access Framework instead)
- Fix duplicate entries in generated hosts file
- Fix allowed hosts settings in VPN ad blocking
- Fix backup not exported as sdcard not writable
- Fix source update period task preference
- Fix host list paging
Special thanks to holysnipz, ipdev99, QingKongBaiYu, and zgfg for their bug reports.
## [5.0.3] - 2020-05-10
*This version is a pre-release*
- Add TLS support for web server
- Add web server status in preferences
- Add option to install self signed certificate
- Add option to display app icon instead of blank page
- Add full timezone support for source date
- Add workaround for negative source update time when server time is not accurate
- Add follow system dark theme mode
- Fix user host list lost on source update
- Fix import failed toast
- Fix web server not start on install
- Fix duplicate host entry on backup import
- Update mongoose server
- Update translations
Special thanks to saltylemondrops, zgfg, ipdev99 and mickrussom for their bug reports.
## [5.0.2] - 2020-04-25
*This version is a pre-release*
- Fix timezone issues with source modification date
- Fix domain not removed when sources are disabled
- Fix inverted host and ip while generating hosts file
- Fix periodical hosts update check initialization
- Improve overall search feature in list UI
- Improve last online modification date after retrieval
- Fix install snackbar not hiding
- Fix potential deadlock in VPN
- Add missing text on successful VPN update
- Update translations
Special thanks to damoasda for its contribution, Vankog for all translations he merged and Ps24u and dhacke for theirs bug reports.
## [4.3.4] - 2020-04-25
- Fix crash in tcpdump log view on Lollipop
- Fix timezone issues with source modification date
- Fix NDK version
Special thanks to Ps24u and Indranil012 for their bug reports.
## [5.0.1] - 2020-04-15
*This version is a pre-release*
- Fix redirect label from home screen
- Fix preference screen duplication on screen orientation change
- Improve hosts list readability
- Update dependencies
- Fix ndk configuration
Special thanks to TacoTheDank for its contribution and rmw98 and Luniz2k1 for its bug reports.
## [4.3.3] - 2020-04-10
hpHosts service is down.
If you are looking for a replacement, give a try at [StevenBlack's one](https://github.com/StevenBlack/hosts).
- Replace hpHosts default hosts file by StevenBlack hosts file
- Improve tcpdump icons
- Update translations
Special thanks to damoasda its contribution and gallegonovato for its bug report.
## [5.0.0] - 2020-04-07
*This version is a pre-release*
- Add new home screen
- Provides all main controls from one screen
- Displays currently blocked, allowed and redirected domains
- Displays current hosts sources status and control to force apply
- Add non root ad-blocking feature
- Uses a builtin local VPN to filter DNS request to blocked domains
- Based on the work of [dns66 by julian-klode](https://github.com/julian-klode/dns66/issues/39)
- Allows to excluded system applications and per user applications
- Add builtin updater with changelog display
- Add feature to quickly pause and resume ad-blocking
- Add wizard screen for first run setup
- Add feature to display and filter all blocked, allowed, redirect domains
- Improve preferences screen
- Add broadcast receiver to control ad-blocking from third party applications
- Update Android target to Android 10
- Improve root and shell support
- Split translation files to easier understand their context
- Add GitHub action test and build tasks
## [4.3.2] - 2019-12-29
- Fix GitLab source hosting
- Update translations
## [4.3.1] - 2019-12-21
- Update help to include Magisk systemless module for read-only system partition
- Update translations
## [4.3.0] - 2019-11-01
- Fix root not requested
- Improve support for systemless hosts Magisk module
- Update translations
## [4.2.9] - 2019-08-31
- Improve hosts file parsing
- Improve hosts file install error message (add more details than _not enough space_)
- Fix menu drawer translation issue
- Update translations
- Remove the start (opt-in only) telemetry messages
## [4.2.8] - 2019-07-28
- Fix TravisCI build issues
- Update translations
## [4.2.7] - 2019-07-04
- Revert Android gradle plugin to fix F-Droid build issue
## [4.2.6] - 2019-06-23
- Improve backup feature (user lists and hosts source using JSON format)
- Fix F-Droid build issue
- Update translations
Special thanks to RichyHBM its contribution and andy356 for its bug report.
## [4.2.5] - 2019-06-06
- Add Gist and GitLab hosting support for hosts file
- Add option to set default IPv6 redirection
- Improve reboot command
- Improve UI for overlays
- Update translations
Special thanks to MSF-Jarvis and Ralayax for their contributions
## [4.2.4] - 2019-03-23
- Add dedicated no root error message
- Fix connection requirement for automatic update
- Fix crash on TCP dump views when root access is denied
- Fix icon resources and colors
- Improve exception reporting
- Update translations
- Update Android X dependencies
- Update Android Gradle plugin and NDK versions
## [4.2.3] - 2019-03-02
- Fix update check on disabled sources
- Fix cropped label on home screen
- Prevent app installation on external storage (can't launch tcpdump or web server binary)
- Update work manager and material dependencies
## [4.2.2] - 2019-02-16
- Improve Material Theming
- Update build tools
## [4.2.1] - 2019-02-03
- Fix two buttons line when text too long
## [4.2.0] - 2019-02-02
- Add hosts source download cache
- Add snackbar notification to update host from DNS request listing
- Update UI from Material Design to Material Theming
- Update gradle, plugins and dependencies
- Fix crash parsing not defined host source last modified date
- Fix native modules build script (required for F-Droid build server)
- Fix Transifex issues
## [4.1.0] - 2018-12-13
- Add [telemetry feature](https://github.com/AdAway/AdAway/wiki/Telemetry)
- Add snackbar notification to update host when editing hosts sources or lists
- Update translations and fix english locale issues
## [4.0.12] - 2018-11-21
- Fix issue when getting last modified date on file:// hosts source
- Fix excluded hostnames from source due to parser failure
Special thanks to DiamondJohn and Vankog for theirs helpful bug reports.
## [4.0.11] - 2018.11.06
- Update translations from Transifex
- Fix crash using file:// protocol for hosts source
- Fix redirect list import
Special thanks to ipdev, ktmom and shaqman89 for theirs helpful bug reports and Vankog for the locales update.
## [4.0.10] - 2018.10.14
- Last update time now works with GitHub hosted files (on https://raw.githubusercontent.com/ domain)
- Fix infinite "update available" status when at least one host source failed to download
- Fix hosts not installed by the background update service
- Fix hosts source update time when reverting to default hosts file
- Fix "download failed" status when no host source enabled
- Fix a bunch of translation issues
Special thanks to Alain-Olivier and Vankog for theirs contributions and MarcAnt01 for its helpful bug report.
## [4.0.9] - 2018.09.26
- Fix missing reboot and error dialog when installing and checking for hosts update
- Block http hosts source for security
- Add project and support links to the menu
- Fix missing notification channel for Oreo and later
- Fix host name validation to add more complex domain name in black/white lists
- Improve HTTP client connection pool
- Add new internal architecture for hosts installation
- Fix Indonesian locale code
- Clean up a lot a unused resource (texts and graphics)
- Clean up help from unrelated help elements
- Update gradle itself, plugin, ndk and dependencies versions
Special thanks to adem4ik TacoTheDank and @Vankog for theirs contributions and towlie, ipdev for theirs helpful bug reports.
## [4.0.8] - 2018.08.22
- Add option to dismiss welcome card
- Improve hosts update status
- Change background job dependency from Evernote Android Job to Jetpack Work manager
- Update translations
## [4.0.7] - 2018.08.08
- Fix host lists import
- Fix default DNS requests when no log entry
- Update translations from Transifex
Special thanks to Vankog for the translation update and GuardianUSMC and DiamondJohn for the bug reports.
## [4.0.6] - 2018.08.05
- Add web server status (icon and text) in the home card UI
- Fix black and while list inversion bug
## [4.0.5] - 2018.07.02
- Change database to room:
- Add migration from previous database
- Update hosts source UI:
- New animations
- Dialogs validate user data so you do no more loose your input if format is wrong
- New DNS logging UI:
- Add new sort feature:
- by name (old behavior)
- by top level domain name (group entries by DNS so google.com, ads.google.com and www.google.com appears next to each other)
- New controls and animations:
- Block, allow or redirect from the directly from DNS logs
- Currently set up domains (in your lists UI) will be displayed accordingly
- Swipe to refresh!
- Update build tools, target SDK (28) and dependencies
## [4.0.4] - 2018.06.03
- A new light (white) theme
- A new adware UI (using LiveData and ViewModel for the 1st time!)
- A fix for the overlapping status texts in the home screen
## [4.0.3] - 2018.05.20
- Fix tcpdump failed to start after being stopped
## [4.0.2] - 2018.05.09
- Add adaptive launcher icons (8+)
- Add adaptive app shortcut (7.1+)
- Add new hosts content screen
- Fix application title not restored on configuration change
- Fix screen change when opening hosts file
## [4.0.1] - 2018.05.07
- Fix redirection IP and custom target dialogs in preferences
- Fix multiple lines in home card hedear.
## [4.0.0] - 2018.05.06
This version is a major update.
First, There is a new design:
- The home is now card based (webserver card is added if enabled)
- The navigation menu is now an humbugger menu
- The hosts source UI is updated (floating add button, actionbar edition)
- Your lists UI is updated (bottom navigation bar, same controls add, edit, remove like hosts sources)
- Permission request at runtime to access storage to import or export your list
- All other views are updated to use the latest support libraries
And a lot of changes under the hood including:
- Oreo support
- Battery improvement and host update fix
- Better support of root and systemless mode
## [3.3] - 2018.03.05
- Add support for ChainFire's SuperSU "bind sbin" systemless mode - by PerfectSlayer
- Improve systemless activation error handling - by PerfectSlayer
- Improve su location detection - by tstaylor7
- Update Mongoose webserver - by MrRobinson
- Replace CyanogenMod references by LineageOS - by experience7
- Update translations - by Mattter, mission712, ThomasSmallert and muzena
- Fix translation attributes - by Vankog
- Fix Magisk 15.x support - by pec0ra
## [3.2]
- Systemless root support - extensive work by PerfectSlayer
- Improvements to root mounter - by sanjay900
- Translations updates
- Various updates for support on Android 7.x
## [3.1.2]
- Update hosts-file.net source to use https
- Translations updates
## [3.1.1]
- Minor bugfixes
- Update mongoose internal webserver to 6.4 release
- Translations updates
- Update pgl.yoyo.org default source to use https
- Add generated timestamp into header of hosts file
## [3.1]
- Add 64bit arch which allows tcpdump to run on those devices
- Adjust scan adware list and stop it from crashing
- Update libpcap to 1.7.4 release
- Update tcpdump to 4.7.4 release
- Update mongoose internal webserver to 6.0 release and add IPv6 support
- Add enable IPv6 option which adds ::1 to all hosts entries
- Improve building hosts file speed by up to 2.5x
- RevertService uses Target hosts file preference
- Updates to support Android 6.0 SDK (23)
## [3.0.2]
- Disable autocorrect/autocomplete for hosts input on whilelist and redirection inputs
- Allow two pane layout as long as min of 600dp width available regardless of orientation
- Add material colored status icons
- Adjust asset layout per latest development guidelines
- Minor other adjustments
## [3.0.1]
- Make some text fields single line - by Phoenix09
- Make daily update time randomized within a range
- Minor fixes
- Adjust hosts-file.net default source URL
## [3.0]
This release has mainly been done by 0-kaladin.
- Min Android version increased to Android 4.1 to support Position Independent Executables (PIE)
- Material design
## [2.9.2]
This release has mainly been done by Dāvis Mošenkovs.
- Added Trove library for high performance collections
- Adware scan improvements
- Separate whitelisting and redirections options - "Allow whitelisting" defaults to checked; "Allow redirections" defaults to state of "Allow redirections and whitelisting" (or unchecked on new installations)
- Fix tcpdump logging upon file deletion
- Fix possible crash on DB update
- Fix AdAway's default hosts source
## [2.9.1]
- Fixing regression bugs
## [2.9]
This release has mainly been done by Dāvis Mošenkovs, thanks!
- Workaround for Android 4.4 (see Help)
- Fixed Hide reboot dialog setting being ignored after symlink creation
- Fix crashes
- Change AdAway's default hosts source to https
## [2.8.1]
- Fix mobile hosts source
## [2.8]
- Higher timeout for root commands
- New version of RootCommands library (If you experience problems install busybox, AdAway will then use it!)
- Remove unused billing permission
## [2.7]
- Reduce apk size by switching from HtmlSpanner to HtmlTextView library
## [2.6]
- Improve build for F-Droid
## [2.5]
- Fix URLs in-app
## [2.4]
- Fix Remounter for Android 4.3
- Fix auto update on ethernet connection
## [2.3]
- AdAway was removed from Google Play!
- Because http://www.ismeh.com/HOSTS is down, a alternative source has been added
## [2.2]
- Introduce SUPERUSER permission for new Superuser app
- Allow backups of AdAway (for Carbon)
- Logo reworked thanks to Alin Ţoţea-Radu
## [2.1]
- Allow whitelist entries from hosts sources if enabled in preferences
- Fixed missing menu entry for hosts sources on tablets
- Webserver on boot should work more reliable
- Webserver should not be killed on low memory
## [2.0]
IF YOU HAVE PROBLEMS WITH 2.0: Please uninstall and then reinstall AdAway!
- New library for root access: RootCommands
- Tcpdump and Webserver now included for ARM, x86, MIPS (Please test and report problems!)
- Google forced me to remove the possibility to donate via Flattr and PayPal
- Hopefully fixes DNS logging for Android 4.1
## [1.37]
- Skip unreachable hosts sources and show number of successful sources
- Fix for crash on donations screen (Android 4.1)
- Fix crash on help screen (Android 4.1)
- Bind local webserver https only to localhost (thanks to Stebalien for finding that bug)
## [1.36]
- Help and About screens reworked
- Simple scanner for bad Adware apps like Airpush Notifications (derived from open source Airpush-Detector, thanks!)
## [1.35]
- New method for background update checking
- AdAway will now reschedule the update check to execute it when the Internet connection is established
- New preference to update only when on Wifi
- AdAway now allows empty hosts sources for people who only want to maintain their own lists
## [1.34]
- In-app PayPal donations are now possible
- Disabled hardware acceleration on ICS, caused black screens and glitches on some custom roms
## [1.33]
- Fixed rare problems on some devices regarding remounting /system as read/write
## [1.32]
- Fixes crashes with 1.31
## [1.31]
- Check for APN proxy
## [1.30]
- Design improvements for Android 4
- Layout fixes for "Your Lists"
## [1.29]
- Languages updated
- Fixed copying when no cp command is available
## [1.28]
- Now works on devices without cp command
- New debug setting
## [1.27]
- No longer needs busybox
- Fixed problems with reverting
- Method to restart Android changed
## [1.26]
- Usability improvements: Buttons will now be disabled while applying, reverting is improved
- Faster due to improved hosts parsing
- Now including tcpdump
## [1.25]
- Better tcpdump integration
- Wildcard characters * and ? can be used in your whitelist
- Updated translations
- Fixes stuck on applying hopefully
## [1.24]
- Tcpdump DNS request logging
- Preference to allow redirection rules from Hosts Sources
- New hosts source for mobile ads: http://www.ismeh.com/HOSTS
- Webserver binary now updates correctly from old AdAway versions
## [1.23]
- Fix for import/export
## [1.22]
- More notification and status bug fixes
- Fixed preference bug causing automatic update to be enabled
- Fixed color of notifications
- Fixed a crash under Android 3.2
## [1.21]
- Fixed staying notification when update was failing
- Hopefully fixes webserver crashes
## [1.20]
- Fixed staying notification
## [1.19]
- Automatic updating in background can be enabled in preferences
- New method for checking for symlink, should fix some problems
- Fixes for exporting entries
- Fixes for donation screen, when no Google Android Market is available
## [1.18]
- Local webserver now answers with blank page instead of 404 error page
- Import and Export of Your Lists
- Bug fix for symlink check
- Last entry in hosts file is now working (missed new line at end of hosts file)
- Fixes for daily update check
- Allow hostnames without TLD ending
## [1.17]
- Fix for translation problems
## [1.16]
- Translations: German, French, Spanish
Thanks to all contributors!
- Better check for symlink
- Newer version of web server mongoose
- Better handling of AdAway database
- Fixed "Not enough space available" bug
- Fixed problem with applying
## [1.15]
- Added permission for Google Android Market donations
## [1.14]
- Force close on open hosts file fixed
- Donations with Google Android Market added
## [1.13]
- Webserver now hears on all local IP address (0.0.0.0)
- Custom target can be set in preferences, for example to /data/etc/hosts
- Hosts file can be opened from menu
## [1.12]
- Delayed starting of webserver on boot
- Disabled debug logging
## [1.11]
- Fixed bugs in layout
## [1.10]
- Webserver is now a preference, disabled by default
- Fixed daily update again. Should schedule now correctly
- Fixed crash on Android 3.0 and 3.1 in Your Lists
- Fixed preferences on Android 3.x
- Fixed rotation bug on Android 3.x
## [1.09]
- New design for tablet sizes
- New hosts source: http://pgl.yoyo.org/adservers
- fixed crash on Android 3 Honeycomb
- Fix for daily update check
- Removed hosts source sysctl.org because of false positives
## [1.08]
- AdAway got a redesign
- AdAway ships with a webserver, that listens on localhost
- Dates of all hosts sources are saved and can be seen in Hosts sources
- Preference to hide reboot question dialog
- Help page with information about AdAway
- Added new hosts source sysctl.org
- Daily update check can be enabled in preferences
- Should now work on roms which doesn't symlink busybox commands
## [1.07]
- AdAway can now create a symlink
- better error handling
- No update check on orientation change
## [1.06]
- hosts file target can be choosen
- Donation button
- Fixed SQLite bug occuring on Android 2.1
## [1.05]
- Update Check implemented
- Fixed bug when changing orientation of device while downloading
- better error handling when downloading
- Fixed localhost entry again
## [1.04]
- Implemented Redirection List
- Fixed Layout bugs
## [1.03]
- Implemented Blacklist and Whitelist
## [1.02]
- Fixed localhost entry
## [1.01]
- Fixed permissions on /system/etc/hosts

76
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at report [at] adaway.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

169
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,169 @@
# Contributing to AdAway
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to AdAway.
These are mostly guidelines, not rules.
It will help you to understand the project, find answers, deal with the source code and interact with maitainers.
The project is open to any kind of contribution so feel free to share your ideas and participate to the development.
#### Table of contents
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Discovering the project structure](#discovering-the-project-structure)
* [Building the project](#building-the-project)
[How can I contribute?](#how-can-i-contribute)
* [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements)
* [Translating to your language](#translating-to-your-language)
* [Your first code contribution](#your-first-code-contribution)
[Styleguides](#styleguides)
* [Git commit messages](#git-commit-messages)
* [Java styleguide](#java-styleguide)
* [XML styleguide](#xml-styleguide)
[Additional notes](#additional-notes)
* [tcpdump and webserver modules](#tcpdump-and-webserver-modules)
## I don't want to read this whole thing I just have a question!!!
> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below.
We have a dedicated forum with a welcoming community and a wiki to answer your questions:
* [Check the common issues and solutions on the wiki](https://github.com/AdAway/AdAway/wiki/Solutions)
* [Read and post on the dedicated developer forum](https://forum.xda-developers.com/showthread.php?t=2190753)
## What should I know before I get started?
### Discovering the project structure
The AdAway source code is an Android project organized in modules.
There are four main modules:
* `app`: The Android application itself
* `tcpdump`: A module dedicated to build the `pcap` library and the `tcpdump` binary
* `webserver`: A module dedicated to build a simple HTTP server binary based on `mongoose`
* `libraries/RootCommands`: A vendorize Android library to run root shell commands
The three last modules are independent and used by the `app` module.
Modularizing the application allows for faster build times and simplier maintainance.
### Building the project
Building the project will require the latest versions of the Android SDK (Software Development Kit) and NDK (Native Development kit).
They can easily be installed or updated using [Android Studio](https://developer.android.com/studio/).
#### Building with Gradle
1. Ensure you have Android SDK and NDK installed.
If not:
* Option 1: [Install Android Studio](https://developer.android.com/studio/index.html) or,
* Option 2: Install command line tools, build tools and ndk bundle with sdk manager:
`tools/bin/sdkmanager "build-tools;x.y.z" ndk-bundle` where `x.y.z` is the latest version
2. Export `ANDROID_HOME` environment variable pointing to your Android SDK:
`export ANDROID_HOME=/path/to/your/sdk`
3. Launch a build:
`./gradlew assembleRelease`
The first full build of the apk can take a lot of time, about 20 minutes, whereas an incremental build of the `app` module takes less than a dozen seconds.
#### Running on an emulator
In order to test the application on an emulator, disable [the root check in the Constants source file](https://github.com/AdAway/AdAway/blob/c90336cb9b062220540317bc6c7cfedb19927c63/app/src/main/java/org/adaway/util/Constants.java#L28).
## How can I contribute?
### Reporting bugs
> **Note:** Before submitting a bug report, please use [the GitHub search on Issues page](https://github.com/AdAway/AdAway/issues) to check if there is already similar reports.
#### How do I submit a (good) bug report?
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in the most detailed way possible.
* **Provide specific examples to demonstrate the steps**.
Include hosts sources or domains you use, web pages URL you test.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **If you're reporting that AdAway crashed**, include a logcat.
Use `adb logcat` if you have developer settings enabled on your device or use any application like [CatLog](https://play.google.com/store/apps/details?id=com.nolanlawson.logcat) to save logs.
Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to it.
* **Specify which version of AdAway you're using.**
You can get the exact version by opening in-app help and checking the _About_ tab.
* **Specify the Android version and the ROM you're using.**
You can also include any root or customization related information like _Magisk_ or _SuperSU_ version and _Xposed_ modules is installed.
### Suggesting enhancements
#### How do I submit a (good) enhancement suggestion?
Enhancement suggestions are welcome.
After refining your idea or discussing it on the [development forum](https://forum.xda-developers.com/showthread.php?t=2190753), create an issue and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in the most detailed way possible, including specific examples.
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Explain why this enhancement would be useful** to most users.
### Translating to your language
Translations are also welcome.
Moreover, they do not require a development environment, only a web browser.
So if you want to complete or edit your language support for the application, check [the translation guide](TRANSLATING.md).
### Your first code contribution
Unsure where to begin contributing?
You can start by looking through these `good first issue` and `help wanted` issues:
* [Good first issues](https://github.com/AdAway/AdAway/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) - issues which should only require a few lines of code, and a test or two.
* [Help wanted issues](https://github.com/AdAway/AdAway/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - issues which should be a bit more involved than `beginner` issues.
Both issue lists are sorted by total number of comments. While not perfect, the number of comments is a reasonable way of determining the impact a given change will have.
## Style guidelines
### Git commit messages
* Use the present tense ("Add feature" not "Added feature")
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Limit the first line to 80 characters or less
* Reference issues and pull requests liberally after the first line
### Java style guidelines
* Indentation: 4 spaces, no tabs
* Maximum line width for code and comments: 100
* Opening braces don't go on their own line
* Field names: Non-public, non-static fields start with m.
* Acronyms are words: Treat acronyms as words in names, yielding !XmlHttpRequest, getUrl(), etc.
See https://source.android.com/source/code-style.html
### XML style guidelines
* No maximum line width
* Split multiple attributes each on a new line
* Indent using spaces with Indention size 4
## Additional notes
### `tcpdump` and `webserver` modules
#### Origin
Forked from the following sources and slightly modified to compile:
* dnsmasq: https://github.com/CyanogenMod/android_external_dnsmasq
* libpcap: https://github.com/the-tcpdump-group/libpcap/tree/libpcap-1.7.4
* tcpdump: https://github.com/the-tcpdump-group/tcpdump/tree/tcpdump-4.7.4
#### Changes
Please review the following commits for the changes made to the sources above in order for them to compile in this project:
* Commit: https://github.com/AdAway/AdAway/commit/1f4ccb3cec3758757341ad90813506fc2a8fdf7b
* Commit: https://github.com/AdAway/AdAway/commit/289df896c0ac4f96bd862e8a5054f1011ec07cac
* Commit: https://github.com/AdAway/AdAway/commit/08da0745b0732b94221c0f5746160fef8126fd99

674
LICENSE.md Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
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
them 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.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
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
state 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 as published by
the Free Software Foundation, either version 3 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, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU 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. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

120
README.md
View file

@ -1,3 +1,119 @@
# adaway # ![AdAway logo](app/src/main/res/mipmap-mdpi/icon.png) AdAway
Android Ad Blocker [![Build Status](https://github.com/adaway/adaway/actions/workflows/android-ci.yml/badge.svg)](https://github.com/AdAway/AdAway/actions/workflows/android-ci.yml)
[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=org.adaway&metric=security_rating)](https://sonarcloud.io/project/overview?id=org.adaway)
[![GitHub Downloads](https://img.shields.io/github/downloads/adaway/adaway/total?logo=github)](https://github.com/AdAway/AdAway/releases)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/perfectslayer?logo=github)](https://github.com/sponsors/PerfectSlayer)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](/LICENSE.md)
AdAway is an open source ad blocker for Android using the hosts file and local vpn.
[<img src="metadata/en-US/phoneScreenshots/screenshot1.png"
alt="Home screen"
height="256">](metadata/en-US/phoneScreenshots/screenshot1.png)
[<img src="metadata/en-US/phoneScreenshots/screenshot2.png"
alt="Preferences screen"
height="256">](metadata/en-US/phoneScreenshots/screenshot2.png)
[<img src="metadata/en-US/phoneScreenshots/screenshot3.png"
alt="Root based ad blocker screen"
height="256">](metadata/en-US/phoneScreenshots/screenshot3.png)
[<img src="metadata/en-US/phoneScreenshots/screenshot4.png"
alt="Backup and restore screen"
height="256">](metadata/en-US/phoneScreenshots/screenshot4.png)
[<img src="metadata/en-US/phoneScreenshots/screenshot5.png"
alt="Help screen"
height="256">](metadata/en-US/phoneScreenshots/screenshot5.png)
For more information visit https://adaway.org
## Installing
There are two kinds of release:
* The preview builds: on the bleeding edge of development - for testers or adventurous
* The stable builds: ready for every day usage - for end users
### Preview builds
**Requirements:** Android 8 _Oreo_ or above
For users with bugs, there may be preview builds available from the [XDA development thread](https://forum.xda-developers.com/showthread.php?t=2190753) and [AdAway official website](https://app.adaway.org/beta.apk).
It is recommended to try those builds to see if your issue is resolved before creating an issue.
The preview builds may contain bug fixes or new features for new android versions.
[<img src="Resources/get-it-on-adaway.png"
alt="Get it on official AdAway website"
height="80">](https://app.adaway.org/beta.apk)
[<img src="Resources/XDADevelopers.png"
raw="true"
alt="Get it on XDA forum"
height="60">](https://forum.xda-developers.com/showthread.php?t=2190753)
### Stable builds
**Requirements:**
* Android Android 8 _Oreo_ or above
After preview builds have been tested by the more technical or responsive community within the forums, we will then post the stable build to F-Droid.
[<img src="Resources/get-it-on-adaway.png"
alt="Get it on official AdAway website"
height="80">](https://app.adaway.org/adaway.apk)
[<img src="Resources/get-it-on-fdroid.png"
raw="true"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/app/org.adaway)
For devices older than Android 8 _Oreo_, use the version 4 of AdAway.
## Get Host File Sources
See the [Wiki](https://github.com/AdAway/AdAway/wiki), in particular the page [HostsSources](https://github.com/AdAway/AdAway/wiki/HostsSources) for an assorted list of sources you can use in AdAway.
Add the ones you like to the AdAway "Hosts sources" section.
## Getting Help
You can post [Issues](https://github.com/AdAway/AdAway/issues) here or obtain more detailed community support via the [XDA developer thread](http://forum.xda-developers.com/showthread.php?t=2190753).
## Contributing
You want to be involved in the project? Welcome onboard!
Check [the contributing guide](CONTRIBUTING.md) to learn how to report bugs, suggest features and make you first code contribution :+1:
If you are looking for translating the application in your language, [the translating guide](TRANSLATING.md) is for you.
## Project Status
AdAway is actively developed by:
* Bruce Bujon ([@PerfectSlayer](https://github.com/PerfectSlayer)) - Developer
[PayPal](https://paypal.me/BruceBUJON) | [GitHub Sponsorship](https://github.com/sponsors/PerfectSlayer)
* Daniel Mönch ([@Vankog](https://github.com/Vankog)) - Translations
* Jawz101 ([@jawz101](https://github.com/jawz101)) - Hosts list
* Anxhelo Lushka ([@AnXh3L0](https://github.com/AnXh3L0)) - Web site
We do not forget the past maintainers:
* Dāvis Mošenkovs ([@DavisNT](https://github.com/DavisNT)) - Developer
[Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5GUHNXYE58RZS&lc=US&item_name=AdAway%20Donation&no_note=0&no_shipping=1)
* [@0-kaladin](https://github.com/0-kaladin) - Developer and XDA OP
* Sanjay Govind ([@sanjay900](https://github.com/sanjay900)) - Developer
And we thank a lot to the original author:
* Dominik Schürmann ([@dschuermann](https://github.com/dschuermann)) - Original developer
[Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=android%40schuermann.eu&lc=US&item_name=AdAway%20Donation&no_note=0&no_shipping=1&currency_code=EUR) | [Flattr](https://flattr.com/thing/369138/AdAway-Ad-blocker-for-Android) | BTC: `173kZxbkKuvnF5fa5b7t21kqU5XfEvvwTs`
## Permissions
AdAway requires the following permissions:
* `INTERNET` to download hosts files and application updates. It can send bug reports and telemetry [if the user wants to (opt-in only)](https://github.com/AdAway/AdAway/wiki/Telemetry)
* `ACCESS_NETWORK_STATE` to restart VPN on network connection change
* `RECEIVE_BOOT_COMPLETED` to start the VPN on boot
* `FOREGROUND_SERVICE` to run the VPN service in foreground
* `POST_NOTIFICATIONS` to post notifications about hosts source update, application update and VPN controls. All notifications can be enabled or disabled independently.
* `REQUEST_INSTALL_PACKAGES` to update the application using the builtin updater
* `QUERY_ALL_PACKAGES` to let the user pick the applications to exclude from VPN
## Licenses
AdAway is licensed under the GPLv3+.
The file LICENSE includes the full license text.
For more details, check [the license notes](LICENSE.md).

92
RELEASING.md Normal file
View file

@ -0,0 +1,92 @@
# Releasing
## 1. Checking bugs and technical debt
### Lint checks
Android development tools provide linter to check common errors.
Use `./gradlew :app:lint` to run the linter and produce (human readable) reports as HTML file located at `app/build/reports/lint-results.html`.
> [!IMPORTANT]
> Check no new warning was introduced before releasing.
### SonarCloud analysis
The AdAway application source code is [analyzed by SonarCloud](https://sonarcloud.io/dashboard?id=org.adaway) to find bugs, code smells and compute technical debt.
While the overall score may be not perfect, each new release should not increase it.
> [!IMPORTANT]
> Check no new bug nor debt was introduced before releasing.
## 2. Updating application version
Each version has its own number that follows the [Semantic Versioning](https://semver.org/) principle (starting from version 4).
> [!IMPORTANT]
> Update application version name (`appName`) and code (`appCode`) from the `gradle/libs.versions.toml` catalog file.
## 3. Updating the changelog
The AdAway project provides [a global changelog](CHANGELOG.md).
> [!IMPORTANT]
> Update the changelog to let users know what is inside each new version before releasing it.
## 4. Building release APK
The release apk must be built using the `release` flavor (not `debug`).
Check the [contributing guide for building instructions](CONTRIBUTING.md#building-the-project).
> [!IMPORTANT]
> Rename to release apk file to follow the format: `AdAway-<version_name>-<yyyymmdd>.apk`
Example: _AdAway-6.1.2-20220817.apk_ for the version 6.1.2 built the 08/17/22.
## 5. Distributing release
Before sharing the any release, remember to test it.
Release variant apk does not behave like debug variant.
Same goes for real device versus emulator.
> [!IMPORTANT]
> Final tests should be done with release apk variant on real device.
Once tested, releases are posted on XDA development thread using the following template:
```
Hi all,
<welcoming message about the new version>
[U][SIZE="4"]Changelog:[/SIZE][/U]
[LIST]
[*] Item 1
[*] Item 2
[*] ...
[*] Item n
[/LIST]
[U][SIZE="4"]Thanks:[/SIZE][/U]
Special thanks to <contributors> for theirs contributions and <bug reporters> for theirs helpful bug reports.
[U][SIZE="4"]Download:[/SIZE][/U]
[URL="https://app.adaway.org/adaway.apk"]AdAway <application version>[/URL]
```
### Beta releases
The beta releases are only announced in the XDA development thread.
### Stable releases
The stable releases are distributed through [GitHub releases](https://github.com/AdAway/AdAway/releases) and [F-Droid store](https://f-droid.org/packages/org.adaway/) and are posted of the first post of XDA development thread.
Once ready, create and push a tag on GitHub repository using `vX.Y.Z` format (or `vX.Y.Zb` for pre-releases).
To publish the application in GitHub:
* Create a new version based on this tag,
* Copy the changelog part related to the version as description of the release,
* Upload apk binary to the release.
Pushing a tag will publish the application to F-Droid store.
It might takes some days to update but if it does not, build logs are available at the following address: `https://monitor.f-droid.org/builds/log/org.adaway/<versioncode>`.

BIN
Resources/XDADevelopers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

View file

@ -0,0 +1,9 @@
rm localhost.*
# Add compatibility for MINGW
kernel=$(uname -s)
if [[ $kernel == MINGW* ]]; then
export MSYS_NO_PATHCONV=1
fi
openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -days 1126 -subj /CN=localhost -extensions EXT -config ssl.conf

View file

@ -0,0 +1,15 @@
[dn]
CN=localhost
[req]
distinguished_name = dn
[EXT]
subjectAltName=@alternate_names
keyUsage=digitalSignature
extendedKeyUsage=serverAuth
basicConstraints=CA:true
[alternate_names]
DNS.1=localhost
DNS.2=*.doubleclick.net
DNS.3=*.g.doubleclick.net
IP.1=127.0.0.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

112
Resources/icon.svg Normal file
View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3167"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="icon.svg">
<defs
id="defs3169">
<radialGradient
r="17.497915"
fy="40.636124"
fx="33.772423"
cy="40.636124"
cx="33.772423"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
gradientUnits="userSpaceOnUse"
id="radialGradient3878"
xlink:href="#linearGradient4263"
inkscape:collect="always" />
<linearGradient
id="linearGradient4263">
<stop
id="stop4265"
offset="0"
style="stop-color:#bfbfbf;stop-opacity:1;" />
<stop
id="stop4267"
offset="1"
style="stop-color:#ffffff;stop-opacity:1;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3233"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3238"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.61137216,0,0,0.55249588,-4.1660353,-4.330286)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3241"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.61137216,0,0,0.55249588,-3.9874288,-4.330286)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9180417"
inkscape:cx="32.306708"
inkscape:cy="13.540991"
inkscape:current-layer="svg3167"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1680"
inkscape:window-height="997"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata3172">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#cc0000;fill-opacity:1;stroke:#333333;stroke-width:0.46215421;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
d="M 9.90625 1.21875 L 1.25 9.875 L 1.21875 22.09375 L 9.875 30.75 L 22.09375 30.78125 L 30.75 22.125 L 30.78125 9.90625 L 22.125 1.25 L 9.90625 1.21875 z M 6 8.1875 C 6.6763721 8.1197446 10.285042 9.6406437 14.5625 9.65625 C 16.759542 9.791605 17.570434 10.416353 18.1875 11.0625 C 19.571912 12.366793 19.873633 13.03126 21.625 12.1875 C 22.338438 11.843849 23.228376 9.8114647 24.21875 9.59375 C 24.990847 9.481077 26.122457 10.438725 26.40625 10.71875 C 26.716053 11.02451 26.985342 11.333132 27.28125 11.46875 C 26.265273 11.52259 24.879528 13.892184 24.84375 15.90625 C 25.046345 21.533487 22.49174 21.215441 20 23.96875 C 19.205534 24.725992 18.781983 26.303089 18.78125 27.75 L 17.71875 27.71875 C 17.098386 27.73296 16.405511 26.947039 16.0625 26.40625 C 15.407551 25.314883 14.737631 27.401017 13.71875 25.6875 C 13.7659 25.120872 10.972706 25.011002 12.5625 23.6875 C 16.317406 21.029656 18.172296 20.45754 18.09375 19.65625 C 18.00095 18.709606 15.800894 19.560638 13.8125 19.46875 C 10.527778 19.317028 10.884928 17.249787 10.59375 16.28125 C 7.8361706 14.745664 12.704357 14.015508 10.5625 13.5625 C 9.5159322 13.396095 7.6703939 13.778783 7.28125 11.96875 C 7.6312051 12.401076 12.220507 11.538235 10.375 11.25 C 7.814251 10.088009 7.0930254 10.68347 6.03125 8.5 C 5.8473548 8.2768081 5.8439141 8.2031359 6 8.1875 z "
id="path3852" />
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
Resources/icon.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

356
Resources/icon_old.svg Normal file
View file

@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="72"
id="svg2"
inkscape:export-xdpi="120"
inkscape:export-ydpi="120"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:version="0.48.1 r9760"
sodipodi:version="0.32"
width="72"
version="1.1"
sodipodi:docname="icon.svg"
inkscape:export-filename="/home/ds1/Projekte/AdAway/adaway/org_adaway/res/drawable-xhdpi/icon.png">
<defs
id="defs4">
<linearGradient
id="linearGradient3906">
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="0"
id="stop3908" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0.3588765"
id="stop3910" />
<stop
style="stop-color:#ffffff;stop-opacity:0.43333334;"
offset="0.88415229"
id="stop3912" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3914" />
</linearGradient>
<marker
inkscape:stockid="Tail"
orient="auto"
refY="0.0"
refX="0.0"
id="Tail"
style="overflow:visible">
<g
id="g4692"
transform="scale(-1.2)">
<path
id="path4694"
d="M -3.8048674,-3.9585227 L 0.54352094,0"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path4696"
d="M -1.2866832,-3.9585227 L 3.0617053,0"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path4698"
d="M 1.3053582,-3.9585227 L 5.6537466,0"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path4700"
d="M -3.8048674,4.1775838 L 0.54352094,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path4702"
d="M -1.2866832,4.1775838 L 3.0617053,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path4704"
d="M 1.3053582,4.1775838 L 5.6537466,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
</g>
</marker>
<linearGradient
id="linearGradient4263">
<stop
style="stop-color:#bfbfbf;stop-opacity:1;"
offset="0"
id="stop4265" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop4267" />
</linearGradient>
<linearGradient
id="linearGradient4158">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4160" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4162" />
</linearGradient>
<linearGradient
id="linearGradient3177">
<stop
id="stop3187"
offset="0"
style="stop-color:#ffffff;stop-opacity:0;" />
<stop
id="stop3200"
offset="0.4375"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3191"
offset="0.9375"
style="stop-color:#ffffff;stop-opacity:0.43333334;" />
<stop
id="stop3181"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3167">
<stop
id="stop3169"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3171"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<inkscape:perspective
id="perspective10"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3167"
id="linearGradient3341"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.08737273,0,0,0.08134103,83.682183,966.62592)"
x1="337.49271"
y1="402.10623"
x2="416.38455"
y2="645.40979" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3177"
id="linearGradient3345"
gradientUnits="userSpaceOnUse"
x1="356.35681"
y1="164.76173"
x2="425.96066"
y2="646.65405"
gradientTransform="matrix(0.08334585,0,0,0.08334585,2.6106401,974.71004)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient4275"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915"
gradientTransform="matrix(1,0,0,0.90369811,0,3.9133352)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3906"
id="linearGradient3037"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.08707623,0,0,0.08707623,1.0873306,972.09559)"
x1="266.69391"
y1="164.76173"
x2="414.93436"
y2="619.08826" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3878"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="linearGradient3904"
x1="-23.400173"
y1="8.182107"
x2="8.1473732"
y2="-0.74950886"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1.0"
id="base"
inkscape:current-layer="layer1"
inkscape:cx="-5.8491274"
inkscape:cy="31.447789"
inkscape:document-units="px"
inkscape:guide-bbox="true"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="779"
inkscape:window-width="1278"
inkscape:window-x="0"
inkscape:window-y="19"
inkscape:zoom="6.8942911"
pagecolor="#ffffff"
showgrid="false"
showguides="true"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
<dc:title></dc:title>
<dc:date />
<dc:creator>
<cc:Agent>
<dc:title>Dominik Schürmann</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:publisher>
<dc:subject>
<rdf:Bag />
</dc:subject>
<dc:contributor>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Ebene 1"
transform="translate(0,-980.36215)">
<path
sodipodi:type="star"
style="fill:#cc0000;fill-opacity:1;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path3852"
sodipodi:sides="8"
sodipodi:cx="10.353435"
sodipodi:cy="8.182107"
sodipodi:r1="34.570065"
sodipodi:r2="31.938574"
sodipodi:arg1="1.1510765"
sodipodi:arg2="1.5437756"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 24.440895,39.751599 -2.0082281,40.466448 -21.216057,22.269568 -21.930906,-4.1795557 -3.7340262,-23.387385 22.715097,-24.102234 41.922926,-5.9053538 42.637775,20.54377 z"
transform="matrix(1.020506,0.02898302,-0.02898302,1.020506,25.823557,1007.5416)" />
<rect
y="984.25385"
x="4.0865383"
width="63.578712"
style="fill:none;stroke:none"
id="rect2383"
height="63.578712" />
<path
transform="matrix(0.08352444,0,0,0.08352444,70.275371,967.56248)"
style="opacity:0.5;fill:none;stroke:#ffffff;stroke-width:29.08395386;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
sodipodi:ry="320.21835"
sodipodi:rx="320.21835"
sodipodi:cy="515.97119"
sodipodi:cx="384.86813"
id="path3165"
d="m 705.08649,515.97119 c 0,176.85171 -143.36664,320.21835 -320.21836,320.21835 -176.85171,0 -320.21835,-143.36664 -320.21835,-320.21835 0,-176.85171 143.36664,-320.21835 320.21835,-320.21835 176.85172,0 320.21836,143.36664 320.21836,320.21835 z" />
<path
inkscape:connector-curvature="0"
style="opacity:0.34680134000000001;fill:url(#linearGradient3037);fill-opacity:1;fill-rule:evenodd;stroke:none"
sodipodi:nodetypes="csssssc"
id="path3175"
d="m 66.047903,1004.1251 c 0,5.2718 0.864169,23.3397 -0.180281,23.3932 -2.00487,0.1024 -5.900696,-6.6434 -16.012316,-7.0489 -12.732886,-0.5054 -37.233924,12.3723 -43.041824,7.4175 -1.283128,-1.0946 0.073036,-21.1464 0.073036,-23.9315 0,-15.39176 30.523091,-27.66476 41.801135,-17.19059 8.772231,8.14697 4.460889,4.68423 17.36025,17.36029 z" />
<g
id="g3874"
transform="translate(0,-1.0183706)">
<path
sodipodi:nodetypes="cccscsccccccccssccccc"
inkscape:connector-curvature="0"
id="path4253"
d="m 16.327015,1002.823 c -1.863654,-2.2619 6.211627,2.1146 16.215279,2.1511 4.174788,0.2572 5.753469,1.4661 6.926009,2.6939 2.630641,2.4784 3.20235,3.7349 6.530274,2.1316 1.355664,-0.653 3.024421,-4.5164 4.906317,-4.9301 1.467128,-0.2141 3.619526,1.6381 4.158785,2.1702 0.588684,0.581 1.124104,1.1204 1.686384,1.3781 -1.930547,0.1023 -4.605827,4.6079 -4.673811,8.435 0.384969,10.6928 -4.428822,10.1031 -9.163591,15.3349 -1.509634,1.4389 -2.355972,4.4532 -2.357366,7.2026 l -1.98861,-0.061 c -1.178808,0.027 -2.524329,-1.5071 -3.176114,-2.5347 -1.244525,-2.0738 -2.531327,1.8998 -4.46739,-1.3562 0.0896,-1.0767 -5.168452,-1.2872 -2.147546,-3.8021 7.13502,-5.0504 10.618946,-6.1142 10.469694,-7.6368 -0.176332,-1.7988 -4.359873,-0.1699 -8.138192,-0.3445 -6.241584,-0.2883 -5.554758,-4.2434 -6.10805,-6.0838 -5.239916,-2.9179 4.02799,-4.2906 -0.04194,-5.1514 -1.988675,-0.3162 -5.497532,0.4234 -6.236978,-3.016 0.66498,0.8215 9.366113,-0.8583 5.859306,-1.406 -4.865901,-2.208 -6.234889,-1.0261 -8.25246,-5.1751 z"
style="fill:url(#radialGradient3878);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.11195254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
transform="matrix(2.8140812,0,0,2.8140812,-82.170599,925.02543)"
d="m 47.889123,29.666016 c 0,0.152813 -0.123879,0.276692 -0.276692,0.276692 -0.152814,0 -0.276693,-0.123879 -0.276693,-0.276692 0,-0.152814 0.123879,-0.276693 0.276693,-0.276693 0.152813,0 0.276692,0.123879 0.276692,0.276693 z"
sodipodi:ry="0.27669272"
sodipodi:rx="0.27669272"
sodipodi:cy="29.666016"
sodipodi:cx="47.612431"
id="path4279"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
sodipodi:type="arc" />
</g>
<path
sodipodi:type="star"
style="fill:none;stroke:url(#linearGradient3904);stroke-width:4.1448102;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path3852-5"
sodipodi:sides="8"
sodipodi:cx="10.353435"
sodipodi:cy="8.182107"
sodipodi:r1="34.570065"
sodipodi:r2="31.938574"
sodipodi:arg1="1.1510765"
sodipodi:arg2="1.5437756"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 24.440895,39.751599 -2.0082281,40.466448 -21.216057,22.269568 -21.930906,-4.1795557 -3.7340262,-23.387385 22.715097,-24.102234 41.922926,-5.9053538 42.637775,20.54377 z"
transform="matrix(0.94528338,0.02684664,-0.02684664,0.94528338,26.524882,1008.0673)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
Resources/logo_market.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
Resources/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
Resources/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
Resources/screenshot3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
Resources/screenshot4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
Resources/screenshot5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
Resources/screenshot6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -0,0 +1,358 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="38"
id="svg2"
version="1.1"
inkscape:version="0.48.1 r9760"
sodipodi:docname="status_bar_icon.svg"
inkscape:export-filename="/home/ds1/Projekte/AdAway/org_adaway/res/drawable-ldpi/status_bar_icon.png"
inkscape:export-xdpi="45"
inkscape:export-ydpi="45">
<defs
id="defs4">
<linearGradient
id="linearGradient4354">
<stop
style="stop-color:#828282;stop-opacity:1;"
offset="0"
id="stop4356" />
<stop
style="stop-color:#919191;stop-opacity:1;"
offset="1"
id="stop4358" />
</linearGradient>
<linearGradient
id="linearGradient4346">
<stop
style="stop-color:#828282;stop-opacity:1;"
offset="0"
id="stop4348" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4350" />
</linearGradient>
<linearGradient
id="linearGradient4321">
<stop
style="stop-color:#828282;stop-opacity:1;"
offset="0"
id="stop4323" />
<stop
style="stop-color:#919191;stop-opacity:1;"
offset="1"
id="stop4325" />
</linearGradient>
<filter
id="filter7799"
inkscape:label="Inner Shadow"
inkscape:menu="Shadows and Glows"
inkscape:menu-tooltip="Adds a colorizable drop shadow inside"
color-interpolation-filters="sRGB">
<feGaussianBlur
id="feGaussianBlur7801"
stdDeviation="4"
result="result8" />
<feOffset
id="feOffset7803"
dx="4"
dy="4"
result="result11" />
<feComposite
id="feComposite7805"
in2="result11"
result="result6"
in="SourceGraphic"
operator="in" />
<feFlood
id="feFlood7807"
result="result10"
in="result6"
flood-opacity="1"
flood-color="rgb(0,0,0)" />
<feBlend
id="feBlend7809"
in2="result10"
mode="normal"
in="result6"
result="result12" />
<feComposite
id="feComposite7811"
in2="SourceGraphic"
result="result2"
operator="in" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7845"
id="linearGradient7866"
gradientUnits="userSpaceOnUse"
x1="42.703548"
y1="1017.2917"
x2="60.975151"
y2="1027.8625" />
<linearGradient
id="linearGradient7845">
<stop
id="stop7847"
offset="0"
style="stop-color:#e3e4e3;stop-opacity:1;" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0.33728361"
id="stop7855" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0.77889121"
id="stop7851" />
<stop
id="stop7853"
offset="1"
style="stop-color:#eeeeee;stop-opacity:1;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4321"
id="linearGradient4327"
x1="-138.33235"
y1="986.01508"
x2="388.7706"
y2="764.12341"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.06177898,0,0,0.06177898,-28.368932,961.00956)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4354"
id="linearGradient4360"
x1="19.386446"
y1="1052.9519"
x2="42.776966"
y2="1029.1078"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4354"
id="linearGradient4362"
gradientUnits="userSpaceOnUse"
x1="19.386446"
y1="1052.9519"
x2="42.776966"
y2="1029.1078" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4354"
id="linearGradient4364"
gradientUnits="userSpaceOnUse"
x1="19.386446"
y1="1052.9519"
x2="42.776966"
y2="1029.1078" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4354"
id="linearGradient4366"
gradientUnits="userSpaceOnUse"
x1="19.386446"
y1="1052.9519"
x2="42.776966"
y2="1029.1078" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4354"
id="linearGradient4373"
gradientUnits="userSpaceOnUse"
x1="19.386446"
y1="1052.9519"
x2="42.776966"
y2="1029.1078"
gradientTransform="matrix(0.53359487,0,0,0.53359487,-28.874041,477.57575)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="linearGradient3904"
x1="-23.400173"
y1="8.182107"
x2="8.1473732"
y2="-0.74950886"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4263">
<stop
style="stop-color:#bfbfbf;stop-opacity:1;"
offset="0"
id="stop4265" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop4267" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3878"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<linearGradient
id="linearGradient3252">
<stop
style="stop-color:#bfbfbf;stop-opacity:1;"
offset="0"
id="stop3254" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3256" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3906"
id="linearGradient3037"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.02851737,0,0,0.02851737,0.39670267,1017.5151)"
x1="266.69391"
y1="164.76173"
x2="414.93436"
y2="619.08826" />
<linearGradient
id="linearGradient3906">
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="0"
id="stop3908" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0.3588765"
id="stop3910" />
<stop
style="stop-color:#ffffff;stop-opacity:0.43333334;"
offset="0.88415229"
id="stop3912" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3914" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4321"
id="radialGradient3320"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.40031375,0,0,0.36176279,-1.1666316,1019.8464)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4321"
id="linearGradient4104"
x1="-22.430906"
y1="8.182107"
x2="43.137775"
y2="8.182107"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4321"
id="linearGradient4106"
gradientUnits="userSpaceOnUse"
x1="-23.400173"
y1="8.182107"
x2="8.1473732"
y2="-0.74950886" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4321"
id="linearGradient4005"
gradientUnits="userSpaceOnUse"
x1="-22.430906"
y1="8.182107"
x2="43.137775"
y2="8.182107" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="10.218937"
inkscape:cx="12.813952"
inkscape:cy="24.02604"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1278"
inkscape:window-height="779"
inkscape:window-x="0"
inkscape:window-y="19"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1014.3622)">
<g
id="g3812"
style="fill-rule:evenodd" />
<rect
y="1022.0656"
x="1.1925927"
width="21.908386"
style="fill:none;stroke:none"
id="rect2383"
height="21.908386" />
<path
style="fill:url(#radialGradient3320);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 5.3883104,1028.257 c -0.642191,-0.7795 2.140444,0.7286 5.5875716,0.7412 1.438577,0.088 1.98257,0.5052 2.386612,0.9283 0.906483,0.854 1.103487,1.287 2.250247,0.7345 0.467143,-0.2249 1.042176,-1.5562 1.690652,-1.6988 0.505553,-0.074 1.247241,0.5644 1.433062,0.7478 0.202853,0.2001 0.387351,0.386 0.581105,0.4749 -0.665241,0.036 -1.587106,1.5879 -1.610533,2.9065 0.132656,3.6847 -1.526113,3.4815 -3.157653,5.2842 -0.5202,0.4959 -0.811837,1.5345 -0.812317,2.482 l -0.685249,-0.021 c -0.406202,0.011 -0.86985,-0.5194 -1.094447,-0.8734 -0.428848,-0.7147 -0.872263,0.6545 -1.539404,-0.4674 0.03087,-0.371 -1.7809806,-0.4435 -0.7400166,-1.3101 2.4586346,-1.7404 3.6591496,-2.1069 3.6077196,-2.6315 -0.06076,-0.62 -1.502355,-0.059 -2.804313,-0.1188 -2.1507696,-0.099 -1.9140976,-1.4623 -2.1047556,-2.0965 -1.805606,-1.0054 1.387993,-1.4783 -0.01445,-1.775 -0.685271,-0.1087 -1.894377,0.1459 -2.14918,-1.0393 0.229143,0.2831 3.227439,-0.2957 2.01904,-0.4845 -1.676726,-0.7609 -2.148461,-0.3535 -2.843689,-1.7832 z"
id="path4253"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscsccccccccssccccc" />
<path
sodipodi:type="star"
style="fill:none;stroke:url(#linearGradient4106);stroke-width:7.67192268;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path3852-5"
sodipodi:sides="8"
sodipodi:cx="10.353435"
sodipodi:cy="8.182107"
sodipodi:r1="34.570065"
sodipodi:r2="31.938574"
sodipodi:arg1="1.1510765"
sodipodi:arg2="1.5437756"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 24.440895,39.751599 -2.0082281,40.466448 -21.216057,22.269568 -21.930906,-4.1795557 -3.7340262,-23.387385 22.715097,-24.102234 41.922926,-5.9053538 42.637775,20.54377 z"
transform="matrix(0.32573221,0.00925099,-0.00925099,0.32573221,8.7032455,1030.6012)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -0,0 +1,16 @@
#
# Launch web server from a root shell.
#
APP_FOLDER=$(find /data/app -type d -name "org.adaway-*")
LIB_FOLDER="${APP_FOLDER}/lib/arm64"
WEBSERVER_EXEC="$LIB_FOLDER/libwebserver_exec.so"
WEBSERVER_ARGS="--resources /data/user/0/org.adaway/files/webserver --debug"
if [ ! -e $WEBSERVER_EXEC ]; then
echo "Web server exec not found" >&2
exit 1
fi
echo "Running LD_LIBRARY_PATH=$LIB_FOLDER $WEBSERVER_EXEC $WEBSERVER_ARGS"
LD_LIBRARY_PATH=$LIB_FOLDER $WEBSERVER_EXEC $WEBSERVER_ARGS

78
THIRD_PARTY_LICENSES.md Normal file
View file

@ -0,0 +1,78 @@
# Licenses
AdAway is licensed under the GPLv3+.
The file [LICENSE.md](LICENSE.md) includes the full license text.
## AdAway Application
AdAway 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 3 of the License, or (at your option) any later version.
AdAway 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 AdAway.
If not, see <https://www.gnu.org/licenses/>.
## Third-Party Libraries
* Android Jetpack
https://developer.android.com/jetpack/
Apache License v2
* DNS66
https://github.com/julian-klode/dns66
GPLv3
* dnsjava
https://github.com/dnsjava/dnsjava
BSD-3-Clause License
* Guava
https://github.com/google/guava
Apache License v2
* libsu
https://github.com/topjohnwu/libsu
Apache License v2
* Material Components for Android
https://github.com/material-components/material-components-android
Apache License v2
* Mongoose Webserver
https://github.com/cesanta/mongoose
GPLv2 License
* OkHttp
https://github.com/square/okhttp
Apache License v2
* Openssl
https://www.openssl.org/
[OpenSSL license](https://www.openssl.org/source/license-openssl-ssleay.txt)
* Pcap4J
https://github.com/kaitoy/pcap4j
MIT License
* Sentry Java
https://github.com/getsentry/sentry-java
BSD 3-Clause License
* SLF4J
https://www.slf4j.org/
MIT License
* Tcpdump/Libpcap
https://www.tcpdump.org/
BSD 3-Clause License
## Images
* icon.svg, banner.svg
AdAway by Dominik Schürmann
New version by Alin Ţoţea-Radu
GPLv3
* Menu Icons
Original Android Icons

39
TRANSLATING.md Normal file
View file

@ -0,0 +1,39 @@
# Translating to your language
You can help us with your translation efforts!
Please have a look at ticket No. [AdAway/AdAway#1050](https://github.com/AdAway/AdAway/issues/1050) for more detailed information, questions and discussions!
We apreciate new contributors and translations are perfect for getting started with contributing at Github in general.
Here is the gist:
Translations are managed via the **transifex.com website**! (and Transifex' website alone, that is)
Unfortunately, we cannot merge translations via Github directly. Please follow the steps below instead.
Sorry, but this causes some major synchronization issues if not followed. We have to consolidate many contributions by translators and sync them up to the latest state. That is just not possible via Github.
1. Please go to **https://www.transifex.com/free-software-for-android/adaway/**
1. Login or create a new account (you can conveniently login via Github as well)
1. Enroll into the language you want to contribute to or even submit a request for a new language.
* Please keep in mind that we want to stick to the basic languages where possible (e.g. `sr` for Serbian).
Please refrain to request regional localizations (like `sr_RS`).
1. In your language section, you can browse all available resources and start **translating strings right in your browser**.
* You don't have to download anything. Just click "Translate". The downloads are meant for more advanced use cases.
## Here are some tips for using Transifex:
* Make sure to have an eye on the **"Suggestions", "History", "Context" and "Glossary" tabs** on every entry.
* Mind translation efforts that were already done or suggested.
Basically, »*stand on the shoulders of giants*« where possible.
* Sometimes source strings change only marginally, but their translations get cleared anyway.
You can easily **recover their previous translations** by looking at the "Suggestions" tab. Just make sure they really fit the new source text and edit the translation if needed.
* Help us validate translations by **reviewing others'**.
* Some strings contain **placeholders** - like for HTML tags or numbers.
You can click on them to add them or use keyboard shortcuts (see the page settings for an overview).
* You can point out issues to the Translation Organizers via the **"Comments" tab** or just start a discussion.
* Don't forget to save your work!
* Please don't create any Translation Pull Request here on Github.
* We will integrate your contributions from time to time into the code by exporting from Transifex directly. No need to provide any files from your side. ;-)
* For more information about how to use Transifex, see https://docs.transifex.com/
We will add all Transifex translations from time to time to the app.
For our Translation Organizers: You can use the CLI tool for this to retrieve translated resources from the Transifex server and add them to the Github repo. (See https://docs.transifex.com/client/ for more details about the client tool.)

36
app/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
#Android specific
bin
gen
obj
libs/armeabi
libs/armeabi-v7a
libs/arm64-v8a
libs/mips
libs/mips64
libs/x86
libs/x86_64
local.properties
release.properties
ant.properties
*.class
*.apk
#Gradle
.gradle
build
gradle.properties
#Maven
target
pom.xml.*
#Eclipse
.project
.classpath
.settings
.metadata
#IntelliJ IDEA
.idea
*.iml
/.externalNativeBuild/

149
app/build.gradle Normal file
View file

@ -0,0 +1,149 @@
plugins {
id 'com.android.application'
}
boolean keyStoreDefined = project.hasProperty('signingStoreLocation') &&
project.hasProperty('signingStorePassword') &&
project.hasProperty('signingKeyAlias') &&
project.hasProperty('signingKeyPassword')
repositories {
maven {
url 'https://jitpack.io'
}
}
android {
compileSdk 34
ndkVersion '25.2.9519653'
namespace 'org.adaway'
defaultConfig {
minSdk 26
targetSdk 33
versionCode libs.versions.appCode.get() as int
versionName libs.versions.appName.get()
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"
]
}
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
/*
* To sign release build, create file gradle.properties in ~/.gradle/ with this content:
*
* signingStoreLocation=/home/key.store
* signingStorePassword=xxx
* signingKeyAlias=alias
* signingKeyPassword=xxx
*/
if (keyStoreDefined) {
println "Found signature properties in gradle.properties. Build will be signed."
signingConfigs {
release {
storeFile file(signingStoreLocation)
storePassword signingStorePassword
keyAlias signingKeyAlias
keyPassword signingKeyPassword
}
}
buildTypes.debug.signingConfig = signingConfigs.release
buildTypes.release.signingConfig = signingConfigs.release
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
buildFeatures {
viewBinding true
}
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
buildTypes {
// debug {
// shrinkResources false
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// }
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// Do not abort build if lint finds errors
lint {
disable 'MissingTranslation'
}
}
dependencies {
// Native modules
implementation project(':tcpdump')
implementation project(':webserver')
// AndroidX components
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.fragment:fragment:1.8.1'
// "fragment-ktx" is not used but was added to fix the following dependency error:
// Duplicate class androidx.lifecycle.ViewModelLazy found in modules lifecycle-viewmodel-2.5.0-runtime (androidx.lifecycle:lifecycle-viewmodel:2.5.0) and lifecycle-viewmodel-ktx-2.3.1-runtime
implementation 'androidx.fragment:fragment-ktx:1.8.1'
implementation 'androidx.paging:paging-runtime:3.3.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-paging:2.6.1'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.work:work-runtime:2.8.1'
implementation 'com.google.android.material:material:1.9.0'
// Collections related
implementation libs.guava
// Network related
implementation libs.okhttp3.okhttp
// Logging related
implementation libs.timber
if (keyStoreDefined) {
implementation project(':sentrystub')
} else {
implementation platform('io.sentry:sentry-bom:7.8.0')
implementation('io.sentry:sentry-android')
implementation('io.sentry:sentry-android-fragment')
implementation('io.sentry:sentry-android-timber')
}
// Root related
implementation libs.libsu
// VPN related
implementation libs.bundles.pcap4j
implementation libs.dnsjava
implementation libs.slf4j.android
implementation libs.okhttp.dnsoverhttps
// Test related
testImplementation libs.junit
testImplementation libs.json
androidTestImplementation libs.bundles.androidx.test
androidTestImplementation libs.junit
}

10
app/lint.xml Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- list of issues to configure -->
<issue id="InvalidPackage">
<!-- Ignore dnsjava -->
<ignore path="**/dnsjava-3.0.2*.jar" />
<!-- Ignore pcap4j dependency -->
<ignore path="**/jna-5.3.1.jar"/>
</issue>
</lint>

38
app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,38 @@
-keep public class * extends android.content.ContentProvider
# Temporary fix for androidx preference fragement reference
# See https://issuetracker.google.com/issues/145316223
-keep public class org.adaway.ui.prefs.PrefsBackupRestoreFragment
-keep public class org.adaway.ui.prefs.PrefsRootFragment
-keep public class org.adaway.ui.prefs.PrefsUpdateFragment
-keep public class org.adaway.ui.prefs.PrefsVpnFragment
-keepclassmembers class io.sentry.Sentry {
public static final boolean STUB;
}
-dontobfuscate
### Android Jetpack ###
-dontwarn com.google.**
### Sentry ###
-dontwarn io.sentry.**
### OkHttp ###
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform
# Generated rules from R8
-dontwarn org.bouncycastle.jsse.**
-dontwarn org.conscrypt.**
-dontwarn org.openjsse.**
### dnsjava ###
-dontwarn lombok.Generated
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor

View file

@ -0,0 +1,90 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "5175df445bc75bbbb5ea672750d7b425",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER, PRIMARY KEY(`url`))",
"fields": [
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastLocalModification",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastOnlineModification",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"url"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"5175df445bc75bbbb5ea672750d7b425\")"
]
}
}

View file

@ -0,0 +1,146 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "e9b86296a34de1d881f8530fdf2c535d",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastLocalModification",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastOnlineModification",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": true,
"columnNames": [
"host"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e9b86296a34de1d881f8530fdf2c535d')"
]
}
}

View file

@ -0,0 +1,151 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "ace31b365ff79d4497319c74157538d7",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastLocalModification",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastOnlineModification",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": false,
"columnNames": [
"host"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
}
],
"views": [
{
"viewName": "host_entries",
"createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT `host`, `type`, `redirection` FROM `hosts_lists` WHERE `enabled` = 1 AND ((`type` = 0 AND `host` NOT LIKE (SELECT `host` FROM `hosts_lists` WHERE `enabled` = 1 and `type` = 1)) OR `type` = 2) ORDER BY `host` ASC, `type` DESC, `redirection` ASC"
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ace31b365ff79d4497319c74157538d7')"
]
}
}

View file

@ -0,0 +1,187 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "80b1c1d47fbd109a4f052817c9faf980",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localModificationDate",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "onlineModificationDate",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": false,
"columnNames": [
"host"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "host_entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_host_entries_host",
"unique": true,
"columnNames": [
"host"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_host_entries_host` ON `${TABLE_NAME}` (`host`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '80b1c1d47fbd109a4f052817c9faf980')"
]
}
}

View file

@ -0,0 +1,187 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "80b1c1d47fbd109a4f052817c9faf980",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localModificationDate",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "onlineModificationDate",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": false,
"columnNames": [
"host"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "host_entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_host_entries_host",
"unique": true,
"columnNames": [
"host"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_host_entries_host` ON `${TABLE_NAME}` (`host`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '80b1c1d47fbd109a4f052817c9faf980')"
]
}
}

View file

@ -0,0 +1,211 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "c53f309b3cbcdeda90c9f22573023ac2",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `label` TEXT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `allowEnabled` INTEGER NOT NULL, `redirectEnabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER, `size` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "allowEnabled",
"columnName": "allowEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirectEnabled",
"columnName": "redirectEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localModificationDate",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "onlineModificationDate",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": false,
"columnNames": [
"host"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "host_entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_host_entries_host",
"unique": true,
"columnNames": [
"host"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_host_entries_host` ON `${TABLE_NAME}` (`host`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c53f309b3cbcdeda90c9f22573023ac2')"
]
}
}

View file

@ -0,0 +1,221 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "dccd6211bcef97caed75ea42d7df1b32",
"entities": [
{
"tableName": "hosts_sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `label` TEXT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `allowEnabled` INTEGER NOT NULL, `redirectEnabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER, `entityTag` TEXT, `size` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "allowEnabled",
"columnName": "allowEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirectEnabled",
"columnName": "redirectEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localModificationDate",
"columnName": "last_modified_local",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "onlineModificationDate",
"columnName": "last_modified_online",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "entityTag",
"columnName": "entityTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_sources_url",
"unique": true,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hosts_sources_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": []
},
{
"tableName": "hosts_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sourceId",
"columnName": "source_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_hosts_lists_host",
"unique": false,
"columnNames": [
"host"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_host` ON `${TABLE_NAME}` (`host`)"
},
{
"name": "index_hosts_lists_source_id",
"unique": false,
"columnNames": [
"source_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_hosts_lists_source_id` ON `${TABLE_NAME}` (`source_id`)"
}
],
"foreignKeys": [
{
"table": "hosts_sources",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"source_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "host_entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "redirection",
"columnName": "redirection",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_host_entries_host",
"unique": true,
"columnNames": [
"host"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_host_entries_host` ON `${TABLE_NAME}` (`host`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dccd6211bcef97caed75ea42d7df1b32')"
]
}
}

View file

@ -0,0 +1,151 @@
package org.adaway.db;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_URL;
import static org.adaway.db.entity.ListType.ALLOWED;
import static org.adaway.db.entity.ListType.BLOCKED;
import static org.adaway.db.entity.ListType.REDIRECTED;
import static org.junit.Assert.fail;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.adaway.db.dao.HostEntryDao;
import org.adaway.db.dao.HostListItemDao;
import org.adaway.db.dao.HostsSourceDao;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.HostsSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* This class is a base class for testing database feature.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@RunWith(AndroidJUnit4.class)
public abstract class DbTest {
protected static final int EXTERNAL_SOURCE_ID = 2;
@Rule
public TestRule rule = new InstantTaskExecutorRule();
protected AppDatabase db;
protected HostsSourceDao hostsSourceDao;
protected HostListItemDao hostListItemDao;
protected HostEntryDao hostEntryDao;
protected LiveData<Integer> blockedHostCount;
protected LiveData<Integer> allowedHostCount;
protected LiveData<Integer> redirectedHostCount;
protected HostsSource externalHostSource;
protected static <T> T getOrAwaitValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
if (!latch.await(2, TimeUnit.SECONDS)) {
fail("Failed to get LiveData value in time");
}
//noinspection unchecked
return (T) data[0];
}
@Before
public void init() {
createDb();
loadDao();
createSources();
}
protected void createDb() {
Context context = ApplicationProvider.getApplicationContext();
this.db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class)
.allowMainThreadQueries()
.build();
}
protected void loadDao() {
this.hostsSourceDao = this.db.hostsSourceDao();
this.hostListItemDao = this.db.hostsListItemDao();
this.hostEntryDao = this.db.hostEntryDao();
this.blockedHostCount = this.hostListItemDao.getBlockedHostCount();
this.allowedHostCount = this.hostListItemDao.getAllowedHostCount();
this.redirectedHostCount = this.hostListItemDao.getRedirectHostCount();
}
protected void createSources() {
// Insert at least user source and external source to allow duplicate hosts to be inserted
insertSource(USER_SOURCE_ID, USER_SOURCE_URL);
insertSource(EXTERNAL_SOURCE_ID, "https://adaway.org/hosts.txt");
this.externalHostSource = getSourceFromId(EXTERNAL_SOURCE_ID);
}
@After
public void closeDb() {
this.db.close();
}
protected void insertSource(int id, String url) {
HostsSource source = new HostsSource();
source.setId(id);
source.setLabel(url);
source.setUrl(url);
source.setEnabled(true);
this.hostsSourceDao.insert(source);
}
protected void insertBlockedHost(String host, int sourceId) {
HostListItem item = new HostListItem();
item.setType(BLOCKED);
item.setHost(host);
item.setEnabled(true);
item.setSourceId(sourceId);
this.hostListItemDao.insert(item);
}
protected void insertAllowedHost(String host, int sourceId) {
HostListItem item = new HostListItem();
item.setType(ALLOWED);
item.setHost(host);
item.setEnabled(true);
item.setSourceId(sourceId);
this.hostListItemDao.insert(item);
}
protected void insertRedirectedHost(String host, String redirection, int sourceId) {
HostListItem item = new HostListItem();
item.setType(REDIRECTED);
item.setHost(host);
item.setEnabled(true);
item.setRedirection(redirection);
item.setSourceId(sourceId);
this.hostListItemDao.insert(item);
}
protected HostsSource getSourceFromId(int id) {
return this.hostsSourceDao.getAll()
.stream()
.filter(hostsSource -> hostsSource.getId() == id)
.findAny()
.orElse(null);
}
}

View file

@ -0,0 +1,90 @@
package org.adaway.db;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.adaway.db.entity.HostEntry;
import org.junit.Test;
import java.util.List;
/**
* This class tests {@link org.adaway.db.entity.HostListItem} database manipulations.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class HostDbTest extends DbTest {
@Test
public void testEmptyByDefault() throws InterruptedException {
assertEquals(0, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(0, this.hostEntryDao.getAll().size());
}
@Test
public void testInsertThenDeleteHosts() throws InterruptedException {
// Insert blocked hosts
insertBlockedHost("advertising.apple.com", USER_SOURCE_ID);
insertBlockedHost("an.facebook.com", USER_SOURCE_ID);
this.hostEntryDao.sync();
// Check inserting
assertEquals(2, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(2, this.hostEntryDao.getAll().size());
// Remove block hosts
this.hostListItemDao.deleteUserFromHost("advertising.apple.com");
this.hostEntryDao.sync();
// Check deletion
assertEquals(1, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
}
@Test
public void testDuplicateBlockedHosts() throws InterruptedException {
insertBlockedHost("advertising.apple.com", USER_SOURCE_ID);
insertBlockedHost("advertising.apple.com", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
}
@Test
public void testDuplicateAllowedHosts() throws InterruptedException {
insertAllowedHost("adaway.org", USER_SOURCE_ID);
insertAllowedHost("adaway.org", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, this.hostEntryDao.getAll().size());
}
@Test
public void testDuplicateRedirectedHosts() throws InterruptedException {
insertRedirectedHost("github.com", "1.1.1.1", USER_SOURCE_ID);
insertRedirectedHost("github.com", "2.2.2.2", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
}
@Test
public void testRedirectionPriority() throws InterruptedException {
// Insert two redirects for the same host
insertRedirectedHost("adaway.org", "1.1.1.1", USER_SOURCE_ID);
insertRedirectedHost("adaway.org", "2.2.2.2", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted redirected hosts
assertEquals(1, getOrAwaitValue(this.redirectedHostCount).intValue());
// Test inserted redirect
List<HostEntry> entries = this.hostEntryDao.getAll();
assertEquals(1, entries.size());
// Test user redirect is applied in priority
HostEntry entry = this.hostEntryDao.getEntry("adaway.org");
assertNotNull(entry);
assertEquals("1.1.1.1", entry.getRedirection());
}
}

View file

@ -0,0 +1,113 @@
package org.adaway.db;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.junit.Assert.assertEquals;
import org.adaway.db.entity.HostsSource;
import org.junit.Test;
import java.util.List;
/**
* This class tests {@link HostsSource} database manipulations.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class SourceDbTest extends DbTest {
@Test
public void testSourceCount() {
// Test only external source is found
List<HostsSource> sources = this.hostsSourceDao.getAll();
assertEquals(1, sources.size());
assertEquals("https://adaway.org/hosts.txt", sources.get(0).getUrl());
}
@Test
public void testSourceDeletion() throws InterruptedException {
// Insert blocked hosts
insertBlockedHost("bingads.microsoft.com", EXTERNAL_SOURCE_ID);
insertBlockedHost("ads.yahoo.com", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted blocked hosts
assertEquals(2, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(2, this.hostEntryDao.getAll().size());
// Delete source
this.hostsSourceDao.delete(this.externalHostSource);
this.hostEntryDao.sync();
List<HostsSource> sources = this.hostsSourceDao.getAll();
assertEquals(0, sources.size());
// Check related hosts cleaning
assertEquals(0, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(0, this.hostEntryDao.getAll().size());
}
@Test
public void testBlockedHostsFromDisabledSource() throws InterruptedException {
// Insert blocked hosts
insertBlockedHost("advertising.apple.com", USER_SOURCE_ID);
insertBlockedHost("an.facebook.com", USER_SOURCE_ID);
insertBlockedHost("ads.google.com", USER_SOURCE_ID);
insertBlockedHost("bingads.microsoft.com", EXTERNAL_SOURCE_ID);
insertBlockedHost("ads.yahoo.com", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted blocked hosts
assertEquals(5, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(5, this.hostEntryDao.getAll().size());
// Disabled external source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(3, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(3, this.hostEntryDao.getAll().size());
// Re-enable external source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(5, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(5, this.hostEntryDao.getAll().size());
}
@Test
public void testAllowedHostsFromDisabledSource() throws InterruptedException {
// Insert blocked and allowed host
insertBlockedHost("adaway.org", USER_SOURCE_ID);
insertAllowedHost("adaway.org", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted blocked hosts
assertEquals(1, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(1, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, this.hostEntryDao.getAll().size());
// Disabled a source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(0, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
// Re-enable a source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(1, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(0, this.hostEntryDao.getAll().size());
}
@Test
public void testRedirectedHostsFromDisabledSource() throws InterruptedException {
// Insert redirected hosts
insertRedirectedHost("github.com", "1.1.1.1", USER_SOURCE_ID);
insertRedirectedHost("github.com", "2.2.2.2", EXTERNAL_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted blocked hosts
assertEquals(1, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
// Disabled a source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
// Re-enable a source
this.hostsSourceDao.toggleEnabled(this.externalHostSource);
this.hostEntryDao.sync();
assertEquals(1, getOrAwaitValue(this.redirectedHostCount).intValue());
assertEquals(1, this.hostEntryDao.getAll().size());
}
}

View file

@ -0,0 +1,157 @@
package org.adaway.db;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.adaway.db.entity.ListType.ALLOWED;
import static org.adaway.db.entity.ListType.BLOCKED;
import static org.adaway.db.entity.ListType.REDIRECTED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.adaway.db.entity.HostEntry;
import org.junit.Test;
/**
* This class the user lists use case where user can freely add blocked, allowed and redirected hosts.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class UserListTest extends DbTest {
@Test
public void testUserList() throws InterruptedException {
testUserBlockedHosts();
testUserAllowedHosts();
testUserRedirectedHosts();
}
protected void testUserBlockedHosts() throws InterruptedException {
// Insert blocked hosts
insertBlockedHost("advertising.apple.com", USER_SOURCE_ID);
insertBlockedHost("an.facebook.com", USER_SOURCE_ID);
insertBlockedHost("ads.google.com", USER_SOURCE_ID);
insertBlockedHost("bingads.microsoft.com", USER_SOURCE_ID);
insertBlockedHost("ads.yahoo.com", USER_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted blocked hosts
assertEquals(5, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(5, this.hostEntryDao.getAll().size());
// Test each host type
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("advertising.apple.com"));
HostEntry entry = this.hostEntryDao.getEntry("advertising.apple.com");
assertNotNull(entry);
assertEquals("advertising.apple.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("an.facebook.com"));
entry = this.hostEntryDao.getEntry("an.facebook.com");
assertNotNull(entry);
assertEquals("an.facebook.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("ads.google.com"));
entry = this.hostEntryDao.getEntry("ads.google.com");
assertNotNull(entry);
assertEquals("ads.google.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("bingads.microsoft.com"));
entry = this.hostEntryDao.getEntry("bingads.microsoft.com");
assertNotNull(entry);
assertEquals("bingads.microsoft.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("ads.yahoo.com"));
entry = this.hostEntryDao.getEntry("ads.yahoo.com");
assertNotNull(entry);
assertEquals("ads.yahoo.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
}
protected void testUserAllowedHosts() throws InterruptedException {
// Insert allowed hosts
insertAllowedHost("*.google.com", USER_SOURCE_ID);
insertAllowedHost("ads.yahoo.com", USER_SOURCE_ID);
insertAllowedHost("adaway.org", USER_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted allowed hosts
assertEquals(3, getOrAwaitValue(this.allowedHostCount).intValue());
// Test overall list
assertEquals(5, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(3, this.hostEntryDao.getAll().size());
// Test each host type
assertEquals(ALLOWED, this.hostEntryDao.getTypeForHost("adaway.org"));
HostEntry entry = this.hostEntryDao.getEntry("adaway.org");
assertNull(entry);
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("advertising.apple.com"));
entry = this.hostEntryDao.getEntry("advertising.apple.com");
assertNotNull(entry);
assertEquals("advertising.apple.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("an.facebook.com"));
entry = this.hostEntryDao.getEntry("an.facebook.com");
assertNotNull(entry);
assertEquals("an.facebook.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(ALLOWED, this.hostEntryDao.getTypeForHost("ads.google.com"));
entry = this.hostEntryDao.getEntry("ads.google.com");
assertNull(entry);
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("bingads.microsoft.com"));
entry = this.hostEntryDao.getEntry("bingads.microsoft.com");
assertNotNull(entry);
assertEquals("bingads.microsoft.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(ALLOWED, this.hostEntryDao.getTypeForHost("ads.yahoo.com"));
entry = this.hostEntryDao.getEntry("ads.yahoo.com");
assertNull(entry);
}
protected void testUserRedirectedHosts() throws InterruptedException {
// Insert redirected hosts
insertRedirectedHost("ads.yahoo.com", "1.2.3.4", USER_SOURCE_ID);
insertRedirectedHost("github.com", "1.2.3.4", USER_SOURCE_ID);
this.hostEntryDao.sync();
// Test inserted redirected hosts
assertEquals(2, getOrAwaitValue(this.redirectedHostCount).intValue());
// Test overall list
assertEquals(5, getOrAwaitValue(this.blockedHostCount).intValue());
assertEquals(3, getOrAwaitValue(this.allowedHostCount).intValue());
assertEquals(5, this.hostEntryDao.getAll().size()); // 3 blocked, 2 redirected
// Test each host type
assertEquals(ALLOWED, this.hostEntryDao.getTypeForHost("adaway.org"));
HostEntry entry = this.hostEntryDao.getEntry("adaway.org");
assertNull(entry);
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("advertising.apple.com"));
entry = this.hostEntryDao.getEntry("advertising.apple.com");
assertNotNull(entry);
assertEquals("advertising.apple.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("an.facebook.com"));
entry = this.hostEntryDao.getEntry("an.facebook.com");
assertNotNull(entry);
assertEquals("an.facebook.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(REDIRECTED, this.hostEntryDao.getTypeForHost("github.com"));
entry = this.hostEntryDao.getEntry("github.com");
assertNotNull(entry);
assertEquals("github.com", entry.getHost());
assertEquals(REDIRECTED, entry.getType());
assertEquals("1.2.3.4", entry.getRedirection());
assertEquals(ALLOWED, this.hostEntryDao.getTypeForHost("ads.google.com"));
entry = this.hostEntryDao.getEntry("ads.google.com");
assertNull(entry);
assertEquals(BLOCKED, this.hostEntryDao.getTypeForHost("bingads.microsoft.com"));
entry = this.hostEntryDao.getEntry("bingads.microsoft.com");
assertNotNull(entry);
assertEquals("bingads.microsoft.com", entry.getHost());
assertEquals(BLOCKED, entry.getType());
assertEquals(REDIRECTED, this.hostEntryDao.getTypeForHost("ads.yahoo.com"));
entry = this.hostEntryDao.getEntry("ads.yahoo.com");
assertNotNull(entry);
assertEquals("ads.yahoo.com", entry.getHost());
assertEquals(REDIRECTED, entry.getType());
assertEquals("1.2.3.4", entry.getRedirection());
}
}

View file

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<permission-group
android:name="org.adaway.permission-group.API"
android:description="@string/permission_group_api_description"
android:label="@string/permission_group_api_label" />
<permission
android:name="org.adaway.permission.SEND_COMMAND"
android:description="@string/permission_send_command_description"
android:label="@string/permission_send_command_label"
android:permissionGroup="org.adaway.permission-group.API"
android:protectionLevel="dangerous" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".AdAwayApplication"
android:allowBackup="true"
android:backupAgent=".model.backup.AppBackupAgent"
android:fullBackupOnly="false"
android:icon="@mipmap/icon"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/Theme.AdAway"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".ui.home.HomeActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.AdAway.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".ui.welcome.WelcomeActivity"
android:exported="false"
android:label="@string/welcome_title"
android:parentActivityName=".ui.home.HomeActivity"
android:theme="@style/Theme.AdAway.NoActionBar.Red" />
<activity
android:name=".ui.hosts.HostsSourcesActivity"
android:exported="false"
android:label="@string/hosts_title"
android:parentActivityName=".ui.home.HomeActivity" />
<activity
android:name=".ui.source.SourceEditActivity"
android:exported="false"
android:label="@string/source_edit_title"
android:parentActivityName=".ui.hosts.HostsSourcesActivity" />
<activity
android:name=".ui.log.LogActivity"
android:label="@string/shortcut_dns_requests"
android:parentActivityName=".ui.home.HomeActivity" />
<activity
android:name=".ui.lists.ListsActivity"
android:exported="false"
android:label="@string/lists_title"
android:launchMode="singleTop"
android:parentActivityName=".ui.home.HomeActivity">
<meta-data
android:name="android.app.searchable"
android:resource="@xml/list_searchable" />
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
</activity>
<activity
android:name=".ui.help.HelpActivity"
android:exported="false"
android:label="@string/menu_help"
android:parentActivityName=".ui.home.HomeActivity" />
<activity
android:name=".ui.support.SupportActivity"
android:exported="false"
android:label="@string/support_title"
android:parentActivityName=".ui.home.HomeActivity"
android:theme="@style/Theme.AdAway.NoActionBar.Red" />
<activity
android:name=".ui.prefs.PrefsActivity"
android:exported="false"
android:label="@string/preferences_drawer_item"
android:parentActivityName=".ui.home.HomeActivity" />
<activity
android:name=".ui.prefs.exclusion.PrefsVpnExcludedAppsActivity"
android:exported="false"
android:label="@string/pref_vpn_exclude_user_apps_activity" />
<activity
android:name=".ui.update.UpdateActivity"
android:exported="false"
android:label="@string/update_title"
android:parentActivityName=".ui.home.HomeActivity"
android:theme="@style/Theme.AdAway.NoActionBar.Red" />
<service
android:name=".vpn.VpnService"
android:exported="true"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
<service
android:name=".tile.AdBlockingTileService"
android:exported="true"
android:icon="@drawable/logo"
android:label="@string/app_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<receiver
android:name=".broadcast.BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name=".broadcast.CommandReceiver"
android:exported="true"
android:permission="org.adaway.permission.SEND_COMMAND">
<intent-filter>
<action android:name="org.adaway.action.SEND_COMMAND" />
</intent-filter>
</receiver>
<receiver
android:name=".broadcast.UpdateReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />
<meta-data
android:name="io.sentry.dsn"
android:value="https://8dac17b798fb45e492278a678c5ab028@o209266.ingest.us.sentry.io/1331667" />
<meta-data android:name="io.sentry.traces.user-interaction.enable" android:value="true" />
</application>
</manifest>

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100%"
height="100%"
viewBox="0 0 32 32"
id="svg3167"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="icon-lite.svg">
<defs
id="defs3169">
<radialGradient
r="17.497915"
fy="40.636124"
fx="33.772423"
cy="40.636124"
cx="33.772423"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
gradientUnits="userSpaceOnUse"
id="radialGradient3878"
xlink:href="#linearGradient4263"
inkscape:collect="always" />
<linearGradient
id="linearGradient4263">
<stop
id="stop4265"
offset="0"
style="stop-color:#bfbfbf;stop-opacity:1;" />
<stop
id="stop4267"
offset="1"
style="stop-color:#ffffff;stop-opacity:1;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3233"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.161721,0,0,1.0498451,-2.6955965,978.41529)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3238"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.61137216,0,0,0.55249588,-4.1660353,-4.330286)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4263"
id="radialGradient3241"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.61137216,0,0,0.55249588,-3.9874288,-4.330286)"
cx="33.772423"
cy="40.636124"
fx="33.772423"
fy="40.636124"
r="17.497915" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.836083"
inkscape:cx="5.6448078"
inkscape:cy="16.057137"
inkscape:current-layer="svg3167"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1387"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata3172">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#cc0000;fill-opacity:0.11640212;stroke:#333333;stroke-width:0.46215421000000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.23529412000000000;stroke-dasharray:none;stroke-dashoffset:0"
d="M 9.90625 1.21875 L 1.25 9.875 L 1.21875 22.09375 L 9.875 30.75 L 22.09375 30.78125 L 30.75 22.125 L 30.78125 9.90625 L 22.125 1.25 L 9.90625 1.21875 z M 6 8.1875 C 6.6763721 8.1197446 10.285042 9.6406437 14.5625 9.65625 C 16.759542 9.791605 17.570434 10.416353 18.1875 11.0625 C 19.571912 12.366793 19.873633 13.03126 21.625 12.1875 C 22.338438 11.843849 23.228376 9.8114647 24.21875 9.59375 C 24.990847 9.481077 26.122457 10.438725 26.40625 10.71875 C 26.716053 11.02451 26.985342 11.333132 27.28125 11.46875 C 26.265273 11.52259 24.879528 13.892184 24.84375 15.90625 C 25.046345 21.533487 22.49174 21.215441 20 23.96875 C 19.205534 24.725992 18.781983 26.303089 18.78125 27.75 L 17.71875 27.71875 C 17.098386 27.73296 16.405511 26.947039 16.0625 26.40625 C 15.407551 25.314883 14.737631 27.401017 13.71875 25.6875 C 13.7659 25.120872 10.972706 25.011002 12.5625 23.6875 C 16.317406 21.029656 18.172296 20.45754 18.09375 19.65625 C 18.00095 18.709606 15.800894 19.560638 13.8125 19.46875 C 10.527778 19.317028 10.884928 17.249787 10.59375 16.28125 C 7.8361706 14.745664 12.704357 14.015508 10.5625 13.5625 C 9.5159322 13.396095 7.6703939 13.778783 7.28125 11.96875 C 7.6312051 12.401076 12.220507 11.538235 10.375 11.25 C 7.814251 10.088009 7.0930254 10.68347 6.03125 8.5 C 5.8473548 8.2768081 5.8439141 8.2031359 6 8.1875 z "
id="path3852" />
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIUR9ZJhU2vy/hB7LChIPgUuBEzqkQwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MTAyNzA2NTEwNVoXDTI3MTEy
NzA2NTEwNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAoVNvAzvXCjH07lgGTwBRMOFNuvsIBzi5rtJw8EQI1+np
h8jZjGmTq759VoqsRSrzPSb8W4XNmGlg8gm0nRN9VJXE9IccWQmpbl61MG6zu9Ae
/RZ8iAuydztn16/sOVQMT3Y0dXl3Fz9VtSEAMWZG9iATlVuugShLbod+Smw3mQow
2Z6Mfg6u4vpPCLG13Hdilv+UGs19dUKlFJdsz2M/gsk0vIlyhaFc9PcyzKJG4Tk2
XZOHEWMj7VjmEMZoUogihi9EWPLpBoi86dIWBBQfhu9HTwzl1BE33TMtYLPdKdxD
9I9QPg59wbquiVYZ3sPVISdks4qmg4NgzBJVB/tWhQIDAQABo4GWMIGTMEIGA1Ud
EQQ7MDmCCWxvY2FsaG9zdIIRKi5kb3VibGVjbGljay5uZXSCEyouZy5kb3VibGVj
bGljay5uZXSHBH8AAAEwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFPyh/TOOyMqOumAhxn1Jsyb09CQ3MA0G
CSqGSIb3DQEBCwUAA4IBAQAdxRNksyLjIwVaKjmC+Yx87zKw8bSmH2mm85KJ0Y7z
uoWLdiw5Lc7/uZoXEl4KGJEnAXb83lo2042WCn49bOhAtYv7tOBTvTGhCHCZ6p7u
Lk+dN8eOkfKgIpKv+tyt0y+Dl+K9m0TegBqky03xf+gjfMaNctwkxxl7Ls4CR9Lz
Bufvt1HxJkmSILmTy8ByJPG5ePN6By4YiEwg9h2Hdx7zEqAKkgHvr4Yn/c4GzDQ+
UZZW9NCIsKkaV32UfpCSodlpc+xeayX8jxZW2GnXs31IjJd4KuNfK5g7ZuOIZN0S
iKvTwJkScrfvLlmbCqEJ7Nu4l/XR+5EAy21YNlV+mo7D
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChU28DO9cKMfTu
WAZPAFEw4U26+wgHOLmu0nDwRAjX6emHyNmMaZOrvn1WiqxFKvM9Jvxbhc2YaWDy
CbSdE31UlcT0hxxZCaluXrUwbrO70B79FnyIC7J3O2fXr+w5VAxPdjR1eXcXP1W1
IQAxZkb2IBOVW66BKEtuh35KbDeZCjDZnox+Dq7i+k8IsbXcd2KW/5QazX11QqUU
l2zPYz+CyTS8iXKFoVz09zLMokbhOTZdk4cRYyPtWOYQxmhSiCKGL0RY8ukGiLzp
0hYEFB+G70dPDOXUETfdMy1gs90p3EP0j1A+Dn3Buq6JVhnew9UhJ2SziqaDg2DM
ElUH+1aFAgMBAAECggEAErRYPjE9dP6mzc2h6Z35S+gLeZ7qZt/yU20t0AWrWtFR
lL86Tffdub9ry9FnONvKePAguUHRvRaWuWlbqgyc7uYwgEN8C2y92sCbVGK5bxCp
zyFAzgtBJWbbWtwYUOtIRBxJ58buAmGC/+20FoYruxSsAJixKmNwH4ARKfLTHWis
bwzULTA4fhmvPgv+DWg7rxOTZP+7Jl5xFQnj4lXQaTt44B9DQF7ArtVjwHJWGbAN
muc4Ij/2lzD1EFNv9hXRvPjnhdN8WuDXZ52BTNj3ZXEYYyjw6Mvy7Qtsiz4FovpT
UvIsQgUyvkyukKX+8rQb+JAcfZQHnfXFSoZyIJ/KDwKBgQDQAY7L6o/ZwQ2FjyyN
K0Zu4oozQh1VM6C8eGtOsa6b+1y1d3RQg0JYb4ay1KwyFAUVnDSx/dNVg8LxVbL+
st0EAUYmyNtozAerkAAdbYLr/mDKM4pYp9OzamqJYaUVXDa70zEOe/Q86a4KKa8l
mr6jfd2+OHhBjFfc/UWfZKWAiwKBgQDGjJXM8KvFIqkBnNqcy1NhhtUUAwzV9oye
HsagZBiTDXeCq2FUOJRh1474az+X12PTCDHNobvhGwz+eYe8yfDmILE9RmjbtCwO
YzTFuEyzFxG4aWAVV9/u+qWu8LQFeXRVOyztNFmIXqWaaCg0tahaVKI0dukMk+/1
M4b/fGzXLwKBgCBxfcJUjadbMy63zC0gqNW2w/OGxmh5qwJ6jdIyaJevtyAex6ef
MYP1sT7HaSxObxSVzqpMeuAFsyxNP6P2Zf6v7C80ePR5jmC2Dy6H3DnO7W3caCG3
249Kc9+FuWgBgA//utEViFzP3fN72PO2lTGO+j0nNaqTp0iywF9CJYZNAoGAXo/k
ZKgXVxuL3K3E3Lpl6uQZpZ9SRLFZBZHozcj+f0MBsWVIRKFx4iuU9zG1Ju85pu+X
MLWf0rVcefKNuFeBeUkGwQVAuarU9MFBCA4f0YfiM69USLYCfEI6GNihFJ5kzpcR
baPqJG3Xd3O1+myuUt9OJaiglBH9Tg4NdK7g85cCgYAYL3r9a8LosXGwtqiahX9o
Hyu/lRrKDH6yvUN2YeHN10pyY8rkdLAZ6E6imgxHqA8n8AibhkoaGpTrBlKRgP1/
MMjeqy3C3hYjBQdfs+Vv6YtsufhegHs84/tgDxyfkWcljeXg2GoZvPQxANNZe8QE
JTaivHj2kiF8ZakITrXT+A==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web server test page</title>
</head>
<body>
<h1>Web server works fine</h1>
<img width="60%" src="icon.png" alt="AdAway logo when option is enabled" style="display: block; margin: auto;">
<p>If you can see this page, that means the web server is working fine and its certificate is installed.</p>
</body>
</html>

BIN
app/src/main/icon-web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,76 @@
package org.adaway;
import android.app.Application;
import org.adaway.helper.NotificationHelper;
import org.adaway.helper.PreferenceHelper;
import org.adaway.model.adblocking.AdBlockMethod;
import org.adaway.model.adblocking.AdBlockModel;
import org.adaway.model.source.SourceModel;
import org.adaway.model.update.UpdateModel;
import org.adaway.util.log.ApplicationLog;
/**
* This class is a custom {@link Application} for AdAway app.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class AdAwayApplication extends Application {
/**
* The common source model for the whole application.
*/
private SourceModel sourceModel;
/**
* The common ad block model for the whole application.
*/
private AdBlockModel adBlockModel;
/**
* The common update model for the whole application.
*/
private UpdateModel updateModel;
@Override
public void onCreate() {
// Delegate application creation
super.onCreate();
// Initialize logging
ApplicationLog.init(this);
// Create notification channels
NotificationHelper.createNotificationChannels(this);
// Create models
this.sourceModel = new SourceModel(this);
this.updateModel = new UpdateModel(this);
}
/**
* Get the source model.
*
* @return The common source model for the whole application.
*/
public SourceModel getSourceModel() {
return this.sourceModel;
}
/**
* Get the ad block model.
*
* @return The common ad block model for the whole application.
*/
public AdBlockModel getAdBlockModel() {
// Check cached model
AdBlockMethod method = PreferenceHelper.getAdBlockMethod(this);
if (this.adBlockModel == null || this.adBlockModel.getMethod() != method) {
this.adBlockModel = AdBlockModel.build(this, method);
}
return this.adBlockModel;
}
/**
* Get the update model.
*
* @return Teh common update model for the whole application.
*/
public UpdateModel getUpdateModel() {
return this.updateModel;
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (C) 2011-2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This file is part of AdAway.
*
* AdAway 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 3 of the License, or
* (at your option) any later version.
*
* AdAway 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 AdAway. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.adaway.broadcast;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static org.adaway.model.adblocking.AdBlockMethod.ROOT;
import static org.adaway.model.adblocking.AdBlockMethod.VPN;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.adaway.helper.PreferenceHelper;
import org.adaway.model.adblocking.AdBlockMethod;
import org.adaway.util.WebServerUtils;
import org.adaway.vpn.VpnServiceControls;
import timber.log.Timber;
/**
* This broadcast receiver is executed after boot.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Timber.d("BootReceiver invoked.");
AdBlockMethod adBlockMethod = PreferenceHelper.getAdBlockMethod(context);
// Start web server on boot if enabled in preferences
if (adBlockMethod == ROOT && PreferenceHelper.getWebServerEnabled(context)) {
WebServerUtils.startWebServer(context);
}
if (adBlockMethod == VPN && PreferenceHelper.getVpnServiceOnBoot(context)) {
// Ensure VPN is prepared
Intent prepareIntent = android.net.VpnService.prepare(context);
if (prepareIntent != null) {
context.startActivity(prepareIntent);
}
// Start VPN service if enabled in preferences
VpnServiceControls.start(context);
}
}
}
}

View file

@ -0,0 +1,57 @@
package org.adaway.broadcast;
import android.content.Intent;
import timber.log.Timber;
/**
* This enumerate lists the commands of {@link CommandReceiver}.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public enum Command {
/**
* Start the ad-blocking.
*/
START,
/**
* Stop the ad-blocking.
*/
STOP,
/**
* Unknown command.
*/
UNKNOWN;
private static final String INTENT_EXTRA_COMMAND = "COMMAND";
/**
* Read command from intent.
*
* @param intent The intent to read command from.
* @return The read intent.
*/
public static Command readFromIntent(Intent intent) {
Command command = UNKNOWN;
if (intent != null && intent.hasExtra(INTENT_EXTRA_COMMAND)) {
String commandName = intent.getStringExtra(INTENT_EXTRA_COMMAND);
if (commandName != null) {
try {
command = Command.valueOf(commandName);
} catch (IllegalArgumentException e) {
Timber.w("Failed to read command named %s.", commandName);
}
}
}
return command;
}
/**
* Append command to intent.
*
* @param intent The intent to append command to.
*/
public void appendToIntent(Intent intent) {
intent.putExtra(INTENT_EXTRA_COMMAND, name());
}
}

View file

@ -0,0 +1,53 @@
package org.adaway.broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.adaway.AdAwayApplication;
import org.adaway.model.adblocking.AdBlockModel;
import org.adaway.model.error.HostErrorException;
import org.adaway.util.AppExecutors;
import timber.log.Timber;
/**
* This broadcast receiver listens to commands from broadcast.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class CommandReceiver extends BroadcastReceiver {
/**
* This action allows to send commands to the application. See {@link Command} for extra values.
*/
public static final String SEND_COMMAND_ACTION = "org.adaway.action.SEND_COMMAND";
private static final AppExecutors EXECUTORS = AppExecutors.getInstance();
@Override
public void onReceive(Context context, Intent intent) {
if (SEND_COMMAND_ACTION.equals(intent.getAction())) {
AdBlockModel adBlockModel = ((AdAwayApplication) context.getApplicationContext()).getAdBlockModel();
Command command = Command.readFromIntent(intent);
Timber.i("CommandReceiver invoked with command %s.", command);
EXECUTORS.diskIO().execute(() -> executeCommand(adBlockModel, command));
}
}
private void executeCommand(AdBlockModel adBlockModel, Command command) {
try {
switch (command) {
case START:
adBlockModel.apply();
break;
case STOP:
adBlockModel.revert();
break;
case UNKNOWN:
Timber.i("Failed to run an unsupported command.");
break;
}
} catch (HostErrorException e) {
Timber.w(e, "Failed to apply ad block command " + command + ".");
}
}
}

View file

@ -0,0 +1,28 @@
package org.adaway.broadcast;
import static android.content.Intent.ACTION_MY_PACKAGE_REPLACED;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.adaway.AdAwayApplication;
import timber.log.Timber;
/**
* This broadcast receiver is executed at application update.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class UpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
AdAwayApplication application = (AdAwayApplication) context.getApplicationContext();
String versionName = application.getUpdateModel().getVersionName();
Timber.d("UpdateReceiver invoked");
Timber.i("Application update to version %s", versionName);
}
}
}

View file

@ -0,0 +1,134 @@
package org.adaway.db;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.sqlite.db.SupportSQLiteDatabase;
import org.adaway.R;
import org.adaway.db.converter.ListTypeConverter;
import org.adaway.db.converter.ZonedDateTimeConverter;
import org.adaway.db.dao.HostEntryDao;
import org.adaway.db.dao.HostListItemDao;
import org.adaway.db.dao.HostsSourceDao;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.HostsSource;
import org.adaway.db.entity.HostEntry;
import org.adaway.util.AppExecutors;
import static org.adaway.db.Migrations.MIGRATION_1_2;
import static org.adaway.db.Migrations.MIGRATION_2_3;
import static org.adaway.db.Migrations.MIGRATION_3_4;
import static org.adaway.db.Migrations.MIGRATION_4_5;
import static org.adaway.db.Migrations.MIGRATION_5_6;
import static org.adaway.db.Migrations.MIGRATION_6_7;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_URL;
/**
* This class is the application database based on Room.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Database(entities = {HostsSource.class, HostListItem.class, HostEntry.class}, version = 7)
@TypeConverters({ListTypeConverter.class, ZonedDateTimeConverter.class})
public abstract class AppDatabase extends RoomDatabase {
/**
* The database singleton instance.
*/
private static volatile AppDatabase instance;
/**
* Get the database instance.
*
* @param context The application context.
* @return The database instance.
*/
public static AppDatabase getInstance(Context context) {
if (instance == null) {
synchronized (AppDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"app.db"
).addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
AppExecutors.getInstance().diskIO().execute(
() -> AppDatabase.initialize(context, instance)
);
}
}).addMigrations(
MIGRATION_1_2,
MIGRATION_2_3,
MIGRATION_3_4,
MIGRATION_4_5,
MIGRATION_5_6,
MIGRATION_6_7
).build();
}
}
}
return instance;
}
/**
* Initialize the database content.
*/
private static void initialize(Context context, AppDatabase database) {
// Check if there is no hosts source
HostsSourceDao hostsSourceDao = database.hostsSourceDao();
if (!hostsSourceDao.getAll().isEmpty()) {
return;
}
// User list
HostsSource userSource = new HostsSource();
userSource.setLabel(context.getString(R.string.hosts_user_source));
userSource.setId(USER_SOURCE_ID);
userSource.setUrl(USER_SOURCE_URL);
userSource.setAllowEnabled(true);
userSource.setRedirectEnabled(true);
hostsSourceDao.insert(userSource);
// AdAway official
HostsSource source1 = new HostsSource();
source1.setLabel(context.getString(R.string.hosts_adaway_source));
source1.setUrl("https://adaway.org/hosts.txt");
hostsSourceDao.insert(source1);
// StevenBlack
HostsSource source2 = new HostsSource();
source2.setLabel(context.getString(R.string.hosts_stevenblack_source));
source2.setUrl("https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts");
hostsSourceDao.insert(source2);
// Pete Lowe
HostsSource source3 = new HostsSource();
source3.setLabel(context.getString(R.string.hosts_peterlowe_source));
source3.setUrl("https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext");
hostsSourceDao.insert(source3);
}
/**
* Get the hosts source DAO.
*
* @return The hosts source DAO.
*/
public abstract HostsSourceDao hostsSourceDao();
/**
* Get the hosts list item DAO.
*
* @return The hosts list item DAO.
*/
public abstract HostListItemDao hostsListItemDao();
/**
* Get the hosts entry DAO.
*
* @return The hosts entry DAO.
*/
public abstract HostEntryDao hostEntryDao();
}

View file

@ -0,0 +1,123 @@
package org.adaway.db;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_URL;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* This class declares database schema migrations.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
final class Migrations {
/**
* Private constructor of utility class.
*/
private Migrations() {
}
/**
* The migration script from v1 to v2.
*/
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Add hosts sources id column and migrate data
database.execSQL("CREATE TABLE `hosts_sources_new` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `last_modified_local` INTEGER, `last_modified_online` INTEGER)");
database.execSQL("INSERT INTO `hosts_sources_new` (`id`, `url`, `enabled`) VALUES (" + USER_SOURCE_ID + ", '" + USER_SOURCE_URL + "', 1)");
database.execSQL("INSERT INTO `hosts_sources_new` (`url`, `enabled`, `last_modified_local`, `last_modified_online`) SELECT `url`, `enabled`, `last_modified_local`, `last_modified_online` FROM `hosts_sources`");
database.execSQL("DROP TABLE `hosts_sources`");
database.execSQL("ALTER TABLE `hosts_sources_new` RENAME TO `hosts_sources`");
// Add hosts list source id and migrate data
database.execSQL("CREATE TABLE `hosts_lists_new` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `host` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `redirection` TEXT, `source_id` INTEGER NOT NULL, FOREIGN KEY(`source_id`) REFERENCES `hosts_sources`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )");
database.execSQL("INSERT INTO `hosts_lists_new` (`host`, `type`, `enabled`, `redirection`, `source_id`) SELECT `host`, `type`, `enabled`, `redirection`, " + USER_SOURCE_ID + " FROM `hosts_lists`");
database.execSQL("DROP TABLE `hosts_lists`");
database.execSQL("ALTER TABLE `hosts_lists_new` RENAME TO `hosts_lists`");
// Create index
database.execSQL("CREATE UNIQUE INDEX `index_hosts_sources_url` ON `hosts_sources` (`url`)");
database.execSQL("CREATE UNIQUE INDEX `index_hosts_lists_host` ON `hosts_lists` (`host`)");
database.execSQL("CREATE INDEX `index_hosts_lists_source_id` ON `hosts_lists` (`source_id`)");
}
};
/**
* The migration script from v2 to v3.
*/
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE VIEW `host_entries` AS SELECT `host`, `type`, `redirection` FROM `hosts_lists` WHERE `enabled` = 1 AND ((`type` = 0 AND `host` NOT LIKE (SELECT `host` FROM `hosts_lists` WHERE `enabled` = 1 and `type` = 1)) OR `type` = 2) ORDER BY `host` ASC, `type` DESC, `redirection` ASC");
}
};
/**
* Migration script from v3 to v4.
*/
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Remove unique constraint to hosts_lists.host column
database.execSQL("DROP INDEX `index_hosts_lists_host`");
database.execSQL("CREATE INDEX `index_hosts_lists_host` ON `hosts_lists` (`host`)");
// Update host_entries view
database.execSQL("DROP VIEW `host_entries`");
database.execSQL("CREATE VIEW `host_entries` AS SELECT `host`, `type`, `redirection` FROM `hosts_lists` WHERE `enabled` = 1 AND ((`type` = 0 AND `host` NOT LIKE (SELECT `host` FROM `hosts_lists` WHERE `enabled` = 1 and `type` = 1)) OR `type` = 2) GROUP BY `host` ORDER BY `host` ASC, `type` DESC, `redirection` ASC");
}
};
/**
* Migration script from v4 to v5.
*/
static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Remove host_entries view
database.execSQL("DROP VIEW `host_entries`");
// Create new host_entries table
database.execSQL("CREATE TABLE IF NOT EXISTS `host_entries` (`host` TEXT NOT NULL, `type` INTEGER NOT NULL, `redirection` TEXT, PRIMARY KEY(`host`))");
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_host_entries_host` ON `host_entries` (`host`)");
}
};
/**
* Migration script from v5 to v6.
*/
static final Migration MIGRATION_5_6 = new Migration(5, 6) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Update hosts_sources table
database.execSQL("ALTER TABLE `hosts_sources` ADD `label` TEXT NOT NULL DEFAULT \"\"");
database.execSQL("ALTER TABLE `hosts_sources` ADD `allowEnabled` INTEGER NOT NULL DEFAULT 0");
database.execSQL("ALTER TABLE `hosts_sources` ADD `redirectEnabled` INTEGER NOT NULL DEFAULT 0");
database.execSQL("ALTER TABLE `hosts_sources` ADD `size` INTEGER NOT NULL DEFAULT 0");
// Set default values to new source attributes
database.execSQL("UPDATE `hosts_sources` SET `label` = `url`");
// Update user hosts list
database.execSQL("UPDATE `hosts_sources` SET `url` = \"content://org.adaway/user/hosts\", `allowEnabled` = 1, `redirectEnabled` = 1 WHERE `url` = \"file://app/user/hosts\"");
// Update default hosts source label
database.execSQL("UPDATE `hosts_sources` SET `label` = \"AdAway official hosts\" WHERE `url` = \"https://adaway.org/hosts.txt\"");
database.execSQL("UPDATE `hosts_sources` SET `label` = \"StevenBlack Unified hosts\" WHERE `url` = \"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\"");
database.execSQL("UPDATE `hosts_sources` SET `label` = \"Pete Lowe blocklist hosts\" WHERE `url` = \"https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext\"");
// Reset local date to rebuild cache
database.execSQL("UPDATE `hosts_sources` SET `last_modified_local` = NULL");
// Update hosts source date format
database.execSQL("UPDATE `hosts_sources` SET `last_modified_online` = `last_modified_online` / 1000");
// Clear previous file type hosts sources
database.execSQL("DELETE FROM `hosts_sources` WHERE `url` LIKE \"file://%\"");
}
};
/**
* Migration script from v6 to v7.
*/
static final Migration MIGRATION_6_7 = new Migration(6, 7) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Update hosts_sources table
database.execSQL("ALTER TABLE `hosts_sources` ADD `entityTag` TEXT DEFAULT NULL");
}
};
}

View file

@ -0,0 +1,26 @@
package org.adaway.db.converter;
import androidx.room.TypeConverter;
import org.adaway.db.entity.ListType;
/**
* This class is a type converter for Room to support {@link ListType} type.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public final class ListTypeConverter {
private ListTypeConverter() {
// Prevent instantiation
}
@TypeConverter
public static ListType fromValue(Integer value) {
return value == null ? null : ListType.fromValue(value);
}
@TypeConverter
public static Integer typeToValue(ListType type) {
return type == null ? null : type.getValue();
}
}

View file

@ -0,0 +1,30 @@
package org.adaway.db.converter;
import androidx.room.TypeConverter;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import static java.time.ZoneOffset.UTC;
/**
* This class is a type converter for Room to support {@link java.time.ZonedDateTime} type.
* It is stored as a Unix epoc timestamp.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public final class ZonedDateTimeConverter {
private ZonedDateTimeConverter() {
// Prevent instantiation
}
@TypeConverter
public static ZonedDateTime fromTimestamp(Long value) {
return value == null ? null : ZonedDateTime.of(LocalDateTime.ofEpochSecond(value, 0, UTC), UTC);
}
@TypeConverter
public static Long toTimestamp(ZonedDateTime zonedDateTime) {
return zonedDateTime == null ? null : zonedDateTime.toEpochSecond();
}
}

View file

@ -0,0 +1,78 @@
package org.adaway.db.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import org.adaway.db.entity.HostEntry;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.ListType;
import java.util.List;
import java.util.regex.Pattern;
import static androidx.room.OnConflictStrategy.REPLACE;
import static org.adaway.db.entity.ListType.REDIRECTED;
/**
* This interface is the DAO for {@link HostEntry} records.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Dao
public interface HostEntryDao {
Pattern ANY_CHAR_PATTERN = Pattern.compile("\\*");
Pattern A_CHAR_PATTERN = Pattern.compile("\\?");
@Query("DELETE FROM `host_entries`")
void clear();
@Query("INSERT INTO `host_entries` SELECT DISTINCT `host`, `type`, `redirection` FROM `hosts_lists` WHERE `type` = 0 AND `enabled` = 1")
void importBlocked();
@Query("SELECT host FROM hosts_lists WHERE type = 1 AND enabled = 1")
List<String> getEnabledAllowedHosts();
@Query("DELETE FROM `host_entries` WHERE `host` LIKE :hostPattern")
void allowHost(String hostPattern);
@Query("SELECT * FROM hosts_lists WHERE type = 2 AND enabled = 1 ORDER BY host ASC, source_id DESC")
List<HostListItem> getEnabledRedirectedHosts();
@Insert(onConflict = REPLACE)
void redirectHost(HostEntry redirection);
/**
* Synchronize the host entries based on the current hosts lists table records.
*/
default void sync() {
clear();
importBlocked();
for (String allowedHost : getEnabledAllowedHosts()) {
allowedHost = ANY_CHAR_PATTERN.matcher(allowedHost).replaceAll("%");
allowedHost = A_CHAR_PATTERN.matcher(allowedHost).replaceAll("_");
allowHost(allowedHost);
}
for (HostListItem redirectedHost : getEnabledRedirectedHosts()) {
HostEntry entry = new HostEntry();
entry.setHost(redirectedHost.getHost());
entry.setType(REDIRECTED);
entry.setRedirection(redirectedHost.getRedirection());
redirectHost(entry);
}
}
@Query("SELECT * FROM `host_entries` ORDER BY `host`")
List<HostEntry> getAll();
@Query("SELECT `type` FROM `host_entries` WHERE `host` == :host LIMIT 1")
ListType getTypeOfHost(String host);
@Query("SELECT IFNULL((SELECT `type` FROM `host_entries` WHERE `host` == :host LIMIT 1), 1)")
ListType getTypeForHost(String host);
@Nullable
@Query("SELECT * FROM `host_entries` WHERE `host` == :host LIMIT 1")
HostEntry getEntry(String host);
}

View file

@ -0,0 +1,63 @@
package org.adaway.db.dao;
import androidx.lifecycle.LiveData;
import androidx.paging.PagingSource;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import org.adaway.db.entity.HostListItem;
import java.util.List;
import java.util.Optional;
import static androidx.room.OnConflictStrategy.REPLACE;
/**
* This interface is the DAO for {@link HostListItem} entities.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Dao
public interface HostListItemDao {
@Insert(onConflict = REPLACE)
void insert(HostListItem... item);
@Insert(onConflict = REPLACE)
void insert(List<HostListItem> items);
@Update
void update(HostListItem item);
@Delete
void delete(HostListItem item);
@Query("DELETE FROM hosts_lists WHERE source_id = 1 AND host = :host")
void deleteUserFromHost(String host);
@Query("SELECT * FROM hosts_lists WHERE type = :type AND host LIKE :query AND ((:includeSources == 0 AND source_id == 1) || (:includeSources == 1)) GROUP BY host ORDER BY host ASC")
PagingSource<Integer, HostListItem> loadList(int type, boolean includeSources, String query);
@Query("SELECT * FROM hosts_lists ORDER BY host ASC")
List<HostListItem> getAll();
@Query("SELECT * FROM hosts_lists WHERE source_id = 1")
List<HostListItem> getUserList();
@Query("SELECT id FROM hosts_lists WHERE host = :host AND source_id = 1 LIMIT 1")
Optional<Integer> getHostId(String host);
@Query("SELECT COUNT(DISTINCT host) FROM hosts_lists WHERE type = 0 AND enabled = 1")
LiveData<Integer> getBlockedHostCount();
@Query("SELECT COUNT(DISTINCT host) FROM hosts_lists WHERE type = 1 AND enabled = 1")
LiveData<Integer> getAllowedHostCount();
@Query("SELECT COUNT(DISTINCT host) FROM hosts_lists WHERE type = 2 AND enabled = 1")
LiveData<Integer> getRedirectHostCount();
@Query("DELETE FROM hosts_lists WHERE source_id = :sourceId")
void clearSourceHosts(int sourceId);
}

View file

@ -0,0 +1,80 @@
package org.adaway.db.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import org.adaway.db.entity.HostsSource;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import static androidx.room.OnConflictStrategy.IGNORE;
/**
* This interface is the DAO for {@link HostsSource} entities.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Dao
public interface HostsSourceDao {
@Insert(onConflict = IGNORE)
void insert(HostsSource source);
@Update
void update(HostsSource source);
@Delete
void delete(HostsSource source);
@Query("SELECT * FROM hosts_sources WHERE enabled = 1 AND id != 1 ORDER BY url ASC")
List<HostsSource> getEnabled();
default void toggleEnabled(HostsSource source) {
int id = source.getId();
boolean enabled = !source.isEnabled();
source.setEnabled(enabled);
setSourceEnabled(id, enabled);
setSourceItemsEnabled(id, enabled);
}
@Query("UPDATE hosts_sources SET enabled = :enabled WHERE id =:id")
void setSourceEnabled(int id, boolean enabled);
@Query("UPDATE hosts_lists SET enabled = :enabled WHERE source_id =:id")
void setSourceItemsEnabled(int id, boolean enabled);
@Query("SELECT * FROM hosts_sources WHERE id = :id")
Optional<HostsSource> getById(int id);
@Query("SELECT * FROM hosts_sources WHERE id != 1 ORDER BY label ASC")
List<HostsSource> getAll();
@Query("SELECT * FROM hosts_sources WHERE id != 1 ORDER BY label ASC")
LiveData<List<HostsSource>> loadAll();
@Query("UPDATE hosts_sources SET last_modified_online = :dateTime WHERE id = :id")
void updateOnlineModificationDate(int id, ZonedDateTime dateTime);
@Query("UPDATE hosts_sources SET last_modified_local = :localModificationDate, last_modified_online = :onlineModificationDate WHERE id = :id")
void updateModificationDates(int id, ZonedDateTime localModificationDate, ZonedDateTime onlineModificationDate);
@Query("UPDATE hosts_sources SET entityTag = :entityTag WHERE id = :id")
void updateEntityTag(int id, String entityTag);
@Query("UPDATE hosts_sources SET size = (SELECT count(id) FROM hosts_lists WHERE source_id = :id) WHERE id = :id")
void updateSize(int id);
@Query("SELECT count(id) FROM hosts_sources WHERE enabled = 1 AND last_modified_online > last_modified_local")
LiveData<Integer> countOutdated();
@Query("SELECT count(id) FROM hosts_sources WHERE enabled = 1 AND last_modified_online <= last_modified_local")
LiveData<Integer> countUpToDate();
@Query("UPDATE hosts_sources SET last_modified_local = NULL, last_modified_online = NULL, entityTag = NULL, size = 0 WHERE id = :id")
void clearProperties(int id);
}

View file

@ -0,0 +1,50 @@
package org.adaway.db.entity;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
/**
* This entity represents an entry of the built hosts file.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Entity(
tableName = "host_entries",
indices = {@Index(value = "host", unique = true)}
)
public class HostEntry {
@PrimaryKey
@NonNull
private String host;
@NonNull
private ListType type;
private String redirection;
@NonNull
public String getHost() {
return host;
}
public void setHost(@NonNull String host) {
this.host = host;
}
@NonNull
public ListType getType() {
return type;
}
public void setType(@NonNull ListType type) {
this.type = type;
}
public String getRedirection() {
return redirection;
}
public void setRedirection(String redirection) {
this.redirection = redirection;
}
}

View file

@ -0,0 +1,121 @@
package org.adaway.db.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.util.Objects;
import static androidx.room.ForeignKey.CASCADE;
/**
* This entity represents a black, white or redirect list item.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Entity(
tableName = "hosts_lists",
indices = {
@Index(value = "host"),
@Index(value = "source_id")
},
foreignKeys = @ForeignKey(
entity = HostsSource.class,
parentColumns = "id",
childColumns = "source_id",
onUpdate = CASCADE,
onDelete = CASCADE
)
)
public class HostListItem {
@PrimaryKey(autoGenerate = true)
private int id;
@NonNull
private String host;
@NonNull
private ListType type;
private boolean enabled;
private String redirection;
@ColumnInfo(name = "source_id")
private int sourceId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getHost() {
return host;
}
public void setHost(@NonNull String host) {
this.host = host;
}
@NonNull
public ListType getType() {
return type;
}
public void setType(@NonNull ListType type) {
this.type = type;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRedirection() {
return redirection;
}
public void setRedirection(String redirection) {
this.redirection = redirection;
}
public int getSourceId() {
return sourceId;
}
public void setSourceId(int sourceId) {
this.sourceId = sourceId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HostListItem item = (HostListItem) o;
if (id != item.id) return false;
if (enabled != item.enabled) return false;
if (sourceId != item.sourceId) return false;
if (!host.equals(item.host)) return false;
if (type != item.type) return false;
return Objects.equals(redirection, item.redirection);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + host.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + (enabled ? 1 : 0);
result = 31 * result + (redirection != null ? redirection.hashCode() : 0);
result = 31 * result + sourceId;
return result;
}
}

View file

@ -0,0 +1,187 @@
package org.adaway.db.entity;
import android.webkit.URLUtil;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.time.ZonedDateTime;
import java.util.Objects;
import static org.adaway.db.entity.SourceType.FILE;
import static org.adaway.db.entity.SourceType.UNSUPPORTED;
import static org.adaway.db.entity.SourceType.URL;
/**
* This entity represents a source to get hosts list.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
@Entity(
tableName = "hosts_sources",
indices = {@Index(value = "url", unique = true)}
)
public class HostsSource {
/**
* The user source ID.
*/
public static final int USER_SOURCE_ID = 1;
/**
* The user source URL.
*/
public static final String USER_SOURCE_URL = "content://org.adaway/user/hosts";
@PrimaryKey(autoGenerate = true)
private int id;
@NonNull
private String label;
@NonNull
private String url;
private boolean enabled = true;
private boolean allowEnabled = false;
private boolean redirectEnabled = false;
@ColumnInfo(name = "last_modified_local")
private ZonedDateTime localModificationDate;
@ColumnInfo(name = "last_modified_online")
private ZonedDateTime onlineModificationDate;
/**
* The HTTP ETag (strong from, may be <code>null</code>).
*/
private String entityTag;
/**
* The number of hosts list items (<code>0</code> until synced).
*/
private int size;
/**
* Check whether an URL is valid for as host source.<br>
* A valid URL is a HTTPS URL or file URL.
*
* @param url The URL to check.
* @return {@code true} if the URL is valid, {@code false} otherwise.
*/
public static boolean isValidUrl(String url) {
return (!"https://".equals(url) && URLUtil.isHttpsUrl(url)) || URLUtil.isContentUrl(url);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getLabel() {
return label;
}
public void setLabel(@NonNull String label) {
this.label = label;
}
@NonNull
public String getUrl() {
return url;
}
public void setUrl(@NonNull String url) {
this.url = url;
}
public SourceType getType() {
if (this.url.startsWith("https://")) {
return URL;
} else if (this.url.startsWith("content://")) {
return FILE;
} else {
return UNSUPPORTED;
}
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isAllowEnabled() {
return allowEnabled;
}
public void setAllowEnabled(boolean allowEnabled) {
this.allowEnabled = allowEnabled;
}
public boolean isRedirectEnabled() {
return redirectEnabled;
}
public void setRedirectEnabled(boolean redirectEnabled) {
this.redirectEnabled = redirectEnabled;
}
public ZonedDateTime getLocalModificationDate() {
return localModificationDate;
}
public void setLocalModificationDate(ZonedDateTime localModificationDate) {
this.localModificationDate = localModificationDate;
}
public ZonedDateTime getOnlineModificationDate() {
return onlineModificationDate;
}
public void setOnlineModificationDate(ZonedDateTime lastOnlineModification) {
this.onlineModificationDate = lastOnlineModification;
}
public String getEntityTag() {
return this.entityTag;
}
public void setEntityTag(String entityTag) {
this.entityTag = entityTag;
}
public int getSize() {
return this.size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HostsSource that = (HostsSource) o;
if (id != that.id) return false;
if (enabled != that.enabled) return false;
if (!url.equals(that.url)) return false;
if (!Objects.equals(localModificationDate, that.localModificationDate))
return false;
return Objects.equals(onlineModificationDate, that.onlineModificationDate);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + url.hashCode();
result = 31 * result + (enabled ? 1 : 0);
result = 31 * result + (localModificationDate != null ? localModificationDate.hashCode() : 0);
result = 31 * result + (onlineModificationDate != null ? onlineModificationDate.hashCode() : 0);
return result;
}
}

View file

@ -0,0 +1,31 @@
package org.adaway.db.entity;
/**
* This enumerate specifies the type of {@link HostListItem}.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public enum ListType {
BLOCKED(0),
ALLOWED(1),
REDIRECTED(2);
private final int value;
ListType(int value) {
this.value = value;
}
public static ListType fromValue(int value) {
for (ListType listType : ListType.values()) {
if (listType.value == value) {
return listType;
}
}
throw new IllegalArgumentException("Invalid value for list type: " + value);
}
public int getValue() {
return value;
}
}

View file

@ -0,0 +1,21 @@
package org.adaway.db.entity;
/**
* This enumerate specifies the type of {@link HostsSource}.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public enum SourceType {
/**
* The URL type represents online source to download from URL.
*/
URL,
/**
* The FILE type represents file stored source to retrieve using SAF API.
*/
FILE,
/**
* The UNSUPPORTED type represents unhandled source type.
*/
UNSUPPORTED
}

View file

@ -0,0 +1,167 @@
package org.adaway.helper;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.getActivity;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static androidx.core.app.NotificationCompat.PRIORITY_LOW;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import org.adaway.R;
import org.adaway.ui.home.HomeActivity;
import org.adaway.ui.update.UpdateActivity;
/**
* This class is an helper class to deals with notifications.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public final class NotificationHelper {
/**
* The notification channel for updates.
*/
public static final String UPDATE_NOTIFICATION_CHANNEL = "UpdateChannel";
/**
* The notification channel for VPN service.
*/
public static final String VPN_SERVICE_NOTIFICATION_CHANNEL = "VpnServiceChannel";
/**
* The update hosts notification identifier.
*/
private static final int UPDATE_HOSTS_NOTIFICATION_ID = 10;
/**
* The update application notification identifier.
*/
private static final int UPDATE_APP_NOTIFICATION_ID = 11;
/**
* The VPN running service notification identifier.
*/
public static final int VPN_RUNNING_SERVICE_NOTIFICATION_ID = 20;
/**
* The VPN resume service notification identifier.
*/
public static final int VPN_RESUME_SERVICE_NOTIFICATION_ID = 21;
/**
* Private constructor.
*/
private NotificationHelper() {
}
/**
* Create the application notification channel.
*
* @param context The application context.
*/
public static void createNotificationChannels(@NonNull Context context) {
// Create update notification channel
NotificationChannel updateChannel = new NotificationChannel(
UPDATE_NOTIFICATION_CHANNEL,
context.getString(R.string.notification_update_channel_name),
NotificationManager.IMPORTANCE_LOW
);
updateChannel.setDescription(context.getString(R.string.notification_update_channel_description));
// Create VPN service notification channel
NotificationChannel vpnServiceChannel = new NotificationChannel(
VPN_SERVICE_NOTIFICATION_CHANNEL,
context.getString(R.string.notification_vpn_channel_name),
NotificationManager.IMPORTANCE_LOW
);
updateChannel.setDescription(context.getString(R.string.notification_vpn_channel_description));
// Register the channels with the system; you can't change the importance or other notification behaviors after this
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(updateChannel);
notificationManager.createNotificationChannel(vpnServiceChannel);
}
}
/**
* Show the notification about new hosts update available.
*
* @param context The application context.
*/
public static void showUpdateHostsNotification(@NonNull Context context) {
// Get notification manager
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
if (notificationManager == null || !notificationManager.areNotificationsEnabled()) {
return;
}
// Build notification
int color = context.getColor(R.color.notification);
String title = context.getString(R.string.notification_update_host_available_title);
String text = context.getString(R.string.notification_update_host_available_text);
Intent intent = new Intent(context, HomeActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, UPDATE_NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.logo)
.setColorized(true)
.setColor(color)
.setShowWhen(false)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)
.setPriority(PRIORITY_LOW)
.setAutoCancel(true);
// Notify the built notification
notificationManager.notify(UPDATE_HOSTS_NOTIFICATION_ID, builder.build());
}
/**
* Show the notification about new application update available.
*
* @param context The application context.
*/
public static void showUpdateApplicationNotification(@NonNull Context context) {
// Get notification manager
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
if (notificationManager == null || !notificationManager.areNotificationsEnabled()) {
return;
}
// Build notification
int color = context.getColor(R.color.notification);
String title = context.getString(R.string.notification_update_app_available_title);
String text = context.getString(R.string.notification_update_app_available_text);
Intent intent = new Intent(context, UpdateActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, UPDATE_NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.logo)
.setColorized(true)
.setColor(color)
.setShowWhen(false)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)
.setPriority(PRIORITY_LOW)
.setAutoCancel(true);
// Notify the built notification
notificationManager.notify(UPDATE_HOSTS_NOTIFICATION_ID, builder.build());
}
/**
* Hide the notification about new hosts update available.
*
* @param context The application context.
*/
public static void clearUpdateNotifications(@NonNull Context context) {
// Get notification manager
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
if (notificationManager == null) {
return;
}
// Cancel the notification
notificationManager.cancel(UPDATE_HOSTS_NOTIFICATION_ID);
notificationManager.cancel(UPDATE_APP_NOTIFICATION_ID);
}
}

View file

@ -0,0 +1,361 @@
/*
* Copyright (C) 2011-2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This file is part of AdAway.
*
* AdAway 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 3 of the License, or
* (at your option) any later version.
*
* AdAway 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 AdAway. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.adaway.helper;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.appcompat.app.AppCompatDelegate;
import org.adaway.R;
import org.adaway.model.adblocking.AdBlockMethod;
import org.adaway.util.Constants;
import org.adaway.vpn.VpnStatus;
import java.util.Collections;
import java.util.Set;
public final class PreferenceHelper {
private PreferenceHelper() {
}
public static int getDarkThemeMode(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
String pref = prefs.getString(
context.getString(R.string.pref_dark_theme_mode_key),
context.getResources().getString(R.string.pref_dark_theme_mode_def)
);
switch (pref) {
case "MODE_NIGHT_NO":
return AppCompatDelegate.MODE_NIGHT_NO;
case "MODE_NIGHT_YES":
return AppCompatDelegate.MODE_NIGHT_YES;
default:
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
}
}
public static boolean getUpdateCheck(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_check_key),
context.getResources().getBoolean(R.bool.pref_update_check_def)
);
}
public static boolean getNeverReboot(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_never_reboot_key),
context.getResources().getBoolean(R.bool.pref_never_reboot_def)
);
}
public static void setNeverReboot(Context context, boolean value) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(context.getString(R.string.pref_never_reboot_key), value);
editor.apply();
}
public static boolean getEnableIpv6(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_enable_ipv6_key),
context.getResources().getBoolean(R.bool.pref_enable_ipv6_def)
);
}
public static boolean getUpdateCheckAppStartup(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_check_app_startup_key),
context.getResources().getBoolean(R.bool.pref_update_check_app_startup_def)
);
}
public static boolean getUpdateCheckAppDaily(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_check_app_daily_key),
context.getResources().getBoolean(R.bool.pref_update_check_app_daily_def)
);
}
public static boolean getIncludeBetaReleases(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_include_beta_releases_key),
context.getResources().getBoolean(R.bool.pref_update_include_beta_releases_def)
);
}
public static boolean getUpdateCheckHostsDaily(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_check_hosts_daily_key),
context.getResources().getBoolean(R.bool.pref_update_check_hosts_daily_def)
);
}
public static boolean getAutomaticUpdateDaily(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_automatic_update_daily_key),
context.getResources().getBoolean(R.bool.pref_automatic_update_daily_def)
);
}
public static boolean getUpdateOnlyOnWifi(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_update_only_on_wifi_key),
context.getResources().getBoolean(R.bool.pref_update_only_on_wifi_def)
);
}
public static String getRedirectionIpv4(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getString(
context.getString(R.string.pref_redirection_ipv4_key),
context.getString(R.string.pref_redirection_ipv4_def)
);
}
public static String getRedirectionIpv6(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getString(
context.getString(R.string.pref_redirection_ipv6_key),
context.getString(R.string.pref_redirection_ipv6_def)
);
}
public static boolean getWebServerEnabled(Context context) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_webserver_enabled_key),
context.getResources().getBoolean(R.bool.pref_webserver_enabled_def)
);
}
public static boolean getWebServerIcon(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_webserver_icon_key),
context.getResources().getBoolean(R.bool.pref_webserver_icon_def)
);
}
public static AdBlockMethod getAdBlockMethod(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return AdBlockMethod.fromCode(prefs.getInt(
context.getString(R.string.pref_ad_block_method_key),
context.getResources().getInteger(R.integer.pref_ad_block_method_key_def)
));
}
public static void setAbBlockMethod(Context context, AdBlockMethod method) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(context.getString(R.string.pref_ad_block_method_key), method.toCode());
editor.apply();
}
public static VpnStatus getVpnServiceStatus(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return VpnStatus.fromCode(prefs.getInt(
context.getString(R.string.pref_vpn_service_status_key),
context.getResources().getInteger(R.integer.pref_vpn_service_status_def)
));
}
public static void setVpnServiceStatus(Context context, VpnStatus status) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(context.getString(R.string.pref_vpn_service_status_key), status.toCode());
editor.apply();
}
public static boolean getVpnServiceOnBoot(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_vpn_service_on_boot_key),
context.getResources().getBoolean(R.bool.pref_vpn_service_on_boot_def)
);
}
public static boolean getVpnWatchdogEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_vpn_watchdog_enabled_key),
context.getResources().getBoolean(R.bool.pref_vpn_watchdog_enabled_def)
);
}
public static boolean getDebugEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_enable_debug_key),
context.getResources().getBoolean(R.bool.pref_enable_debug_def)
);
}
public static boolean getTelemetryEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_enable_telemetry_key),
context.getResources().getBoolean(R.bool.pref_enable_telemetry_def)
);
}
public static void setTelemetryEnabled(Context context, boolean enabled) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(context.getString(R.string.pref_enable_telemetry_key), enabled);
editor.apply();
}
public static boolean getDisplayTelemetryConsent(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getBoolean(
context.getString(R.string.pref_display_telemetry_consent_key),
context.getResources().getBoolean(R.bool.pref_display_telemetry_consent_def)
);
}
public static void setDisplayTelemetryConsent(Context context, boolean displayTelemetryConsent) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(context.getString(R.string.pref_display_telemetry_consent_key), displayTelemetryConsent);
editor.apply();
}
public static String getVpnExcludedSystemApps(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getString(
context.getString(R.string.pref_vpn_excluded_system_apps_key),
context.getString(R.string.pref_vpn_excluded_system_apps_default)
);
}
public static Set<String> getVpnExcludedApps(Context context) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
return prefs.getStringSet(
context.getString(R.string.pref_vpn_excluded_user_apps_key),
Collections.emptySet()
);
}
public static void setVpnExcludedApps(Context context, Set<String> excludedApplicationPackageNames) {
SharedPreferences prefs = context.getSharedPreferences(
Constants.PREFS_NAME,
Context.MODE_PRIVATE
);
SharedPreferences.Editor editor = prefs.edit();
editor.putStringSet(context.getString(R.string.pref_vpn_excluded_user_apps_key), excludedApplicationPackageNames);
editor.apply();
}
}

View file

@ -0,0 +1,29 @@
package org.adaway.helper;
import android.content.Context;
import androidx.appcompat.app.AppCompatDelegate;
/**
* This class is a helper to apply user selected theme on the application activity.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public final class ThemeHelper {
/**
* Private constructor.
*/
private ThemeHelper() {
}
/**
* Apply the user selected theme.
*
* @param context The context to apply theme.
*/
public static void applyTheme(Context context) {
AppCompatDelegate.setDefaultNightMode(PreferenceHelper.getDarkThemeMode(context));
}
}

View file

@ -0,0 +1,40 @@
package org.adaway.model.adblocking;
import java.util.Arrays;
/**
* This enum represents the ad blocking methods.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public enum AdBlockMethod {
/**
* Not defined ad block method.
*/
UNDEFINED(0),
/**
* The system hosts file ad block method.
*/
ROOT(1),
/**
* The VPN based ad block method.
*/
VPN(2);
private int code;
AdBlockMethod(int code) {
this.code = code;
}
public static AdBlockMethod fromCode(int code) {
return Arrays.stream(AdBlockMethod.values())
.filter(method -> method.code == code)
.findAny()
.orElse(UNDEFINED);
}
public int toCode() {
return this.code;
}
}

View file

@ -0,0 +1,140 @@
package org.adaway.model.adblocking;
import android.content.Context;
import androidx.annotation.StringRes;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.adaway.model.error.HostErrorException;
import org.adaway.model.root.RootModel;
import org.adaway.model.vpn.VpnModel;
import java.util.List;
import timber.log.Timber;
/**
* This class is the base model for all ad block model.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public abstract class AdBlockModel {
/**
* The application context.
*/
protected final Context context;
/**
* The hosts installation status:
* <ul>
* <li>{@code null} if not defined,</li>
* <li>{@code true} if hosts list is installed,</li>
* <li>{@code false} if default hosts file.</li>
* </ul>
*/
protected final MutableLiveData<Boolean> applied;
/**
* The model state.
*/
private final MutableLiveData<String> state;
/**
* Constructor.
*
* @param context The application context.
*/
protected AdBlockModel(Context context) {
this.context = context;
this.state = new MutableLiveData<>();
this.applied = new MutableLiveData<>();
}
/**
* Instantiate ad block model.
*
* @param context The application context.
* @param method The ad block method to get model.
* @return The instantiated model.
*/
public static AdBlockModel build(Context context, AdBlockMethod method) {
switch (method) {
case ROOT:
return new RootModel(context);
case VPN:
return new VpnModel(context);
default:
return new UndefinedBlockModel(context);
}
}
/**
* Get ad block method.
*
* @return The ad block method of this model.
*/
public abstract AdBlockMethod getMethod();
/**
* Checks if hosts list is applied.
*
* @return {@code true} if applied, {@code false} if default.
*/
public LiveData<Boolean> isApplied() {
return this.applied;
}
/**
* Apply hosts list.
*
* @throws HostErrorException If the model configuration could not be applied.
*/
public abstract void apply() throws HostErrorException;
/**
* Revert the hosts list to the default one.
*
* @throws HostErrorException If the model configuration could not be revert.
*/
public abstract void revert() throws HostErrorException;
/**
* Get the model state.
*
* @return The model state.
*/
public LiveData<String> getState() {
return this.state;
}
protected void setState(@StringRes int stateResId, Object... details) {
String state = this.context.getString(stateResId, details);
Timber.d(state);
this.state.postValue(state);
}
/**
* Get whether log are recoding or not.
*
* @return {@code true} if logs are recoding, {@code false} otherwise.
*/
public abstract boolean isRecordingLogs();
/**
* Set log recoding.
*
* @param recording {@code true} to record logs, {@code false} otherwise.
*/
public abstract void setRecordingLogs(boolean recording);
/**
* Get logs.
*
* @return The logs unique and sorted by date, older first.
*/
public abstract List<String> getLogs();
/**
* Clear logs.
*/
public abstract void clearLogs();
}

View file

@ -0,0 +1,59 @@
package org.adaway.model.adblocking;
import static org.adaway.model.adblocking.AdBlockMethod.UNDEFINED;
import static java.util.Collections.emptyList;
import android.content.Context;
import java.util.List;
/**
* This class is a stub model when no ad block method is defined.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class UndefinedBlockModel extends AdBlockModel {
/**
* Constructor.
*
* @param context The application context.
*/
public UndefinedBlockModel(Context context) {
super(context);
}
@Override
public AdBlockMethod getMethod() {
return UNDEFINED;
}
@Override
public void apply() {
// Unsupported operation
}
@Override
public void revert() {
// Unsupported operation
}
@Override
public boolean isRecordingLogs() {
return false;
}
@Override
public void setRecordingLogs(boolean recording) {
// Unsupported operation
}
@Override
public List<String> getLogs() {
return emptyList();
}
@Override
public void clearLogs() {
// Unsupported operation
}
}

View file

@ -0,0 +1,82 @@
package org.adaway.model.backup;
import static org.adaway.util.Constants.PREFS_NAME;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.FileBackupHelper;
import android.app.backup.SharedPreferencesBackupHelper;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.IOException;
import timber.log.Timber;
/**
* This class is a {@link android.app.backup.BackupAgent} to backup and restore application state
* using Android Backup Service. It is based on key-value pairs backup to prevent killing the
* application during the backup (leaving the VPN foreground service always running). It backs up
* and restores the application preferences and the user rules.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class AppBackupAgent extends BackupAgentHelper {
private static final String PREFS_BACKUP_KEY = "prefs";
private static final String RULES_BACKUP_KEY = "rules";
@Override
public void onCreate() {
super.onCreate();
addHelper(PREFS_BACKUP_KEY, new SharedPreferencesBackupHelper(this, PREFS_NAME));
addHelper(RULES_BACKUP_KEY, new SourceBackupHelper(this));
}
/**
* This class is a {@link android.app.backup.BackupHelper} to backup and restore user rules.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
private static class SourceBackupHelper extends FileBackupHelper {
private static final String RULES_FILE_NAME = "rules-backup.json";
private final Context context;
/**
* Constructor.
*
* @param context The application context.
*/
public SourceBackupHelper(Context context) {
super(context, RULES_FILE_NAME);
this.context = context;
}
@Override
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
try {
BackupExporter.exportBackup(this.context, getRulesFileUri());
super.performBackup(oldState, data, newState);
} catch (IOException e) {
Timber.w(e, "Failed to export rules to backup.");
}
}
@Override
public void restoreEntity(BackupDataInputStream data) {
super.restoreEntity(data);
try {
BackupImporter.importBackup(this.context, getRulesFileUri());
} catch (IOException e) {
Timber.w(e, "Failed to import rules from backup.");
}
}
private Uri getRulesFileUri() {
File ruleFile = new File(this.context.getFilesDir(), RULES_FILE_NAME);
return Uri.fromFile(ruleFile);
}
}
}

View file

@ -0,0 +1,141 @@
package org.adaway.model.backup;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.UiThread;
import org.adaway.R;
import org.adaway.db.AppDatabase;
import org.adaway.db.dao.HostListItemDao;
import org.adaway.db.dao.HostsSourceDao;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.HostsSource;
import org.adaway.util.AppExecutors;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.concurrent.Executor;
import static java.util.stream.Collectors.toList;
import static org.adaway.db.entity.ListType.ALLOWED;
import static org.adaway.db.entity.ListType.BLOCKED;
import static org.adaway.db.entity.ListType.REDIRECTED;
import static org.adaway.model.backup.BackupFormat.ALLOWED_KEY;
import static org.adaway.model.backup.BackupFormat.BLOCKED_KEY;
import static org.adaway.model.backup.BackupFormat.REDIRECTED_KEY;
import static org.adaway.model.backup.BackupFormat.SOURCES_KEY;
import static org.adaway.model.backup.BackupFormat.hostToJson;
import static org.adaway.model.backup.BackupFormat.sourceToJson;
import timber.log.Timber;
/**
* This class is a helper class to export user lists and hosts sources to a backup file.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class BackupExporter {
private static final Executor DISK_IO_EXECUTOR = AppExecutors.getInstance().diskIO();
private static final Executor MAIN_THREAD_EXECUTOR = AppExecutors.getInstance().mainThread();
private BackupExporter() {
}
/**
* Export all user lists and hosts sources to a backup file on the external storage.
*
* @param context The application context.
*/
public static void exportToBackup(Context context, Uri backupUri) {
DISK_IO_EXECUTOR.execute(() -> {
boolean imported = true;
try {
exportBackup(context, backupUri);
} catch (IOException e) {
Timber.e(e, "Failed to import backup.");
imported = false;
}
boolean successful = imported;
String fileName = getFileNameFromUri(backupUri);
MAIN_THREAD_EXECUTOR.execute(() -> notifyExportEnd(context, successful, fileName));
});
}
private static String getFileNameFromUri(Uri backupUri) {
String path = backupUri.getPath();
return path == null ? "" : new File(path).getName();
}
@UiThread
private static void notifyExportEnd(Context context, boolean successful, String backupUri) {
Toast.makeText(
context,
context.getString(successful ? R.string.export_success : R.string.export_failed, backupUri),
Toast.LENGTH_LONG
).show();
}
static void exportBackup(Context context, Uri backupUri) throws IOException {
// Open writer on the export file
try (OutputStream outputStream = context.getContentResolver().openOutputStream(backupUri);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
JSONObject backup = makeBackup(context);
writer.write(backup.toString(4));
} catch (JSONException e) {
throw new IOException("Failed to generate backup.", e);
} catch (IOException e) {
throw new IOException("Could not write file.", e);
}
}
private static JSONObject makeBackup(Context context) throws JSONException {
AppDatabase database = AppDatabase.getInstance(context);
HostsSourceDao hostsSourceDao = database.hostsSourceDao();
HostListItemDao hostListItemDao = database.hostsListItemDao();
List<HostListItem> userHosts = hostListItemDao.getUserList();
List<HostListItem> blockedHosts = userHosts.stream()
.filter(value -> value.getType() == BLOCKED)
.collect(toList());
List<HostListItem> allowedHosts = userHosts.stream()
.filter(value -> value.getType() == ALLOWED)
.collect(toList());
List<HostListItem> redirectedHosts = userHosts.stream()
.filter(value -> value.getType() == REDIRECTED)
.collect(toList());
JSONObject backupObject = new JSONObject();
backupObject.put(SOURCES_KEY, buildSourcesBackup(hostsSourceDao.getAll()));
backupObject.put(BLOCKED_KEY, buildListBackup(blockedHosts));
backupObject.put(ALLOWED_KEY, buildListBackup(allowedHosts));
backupObject.put(REDIRECTED_KEY, buildListBackup(redirectedHosts));
return backupObject;
}
private static JSONArray buildSourcesBackup(List<HostsSource> sources) throws JSONException {
JSONArray sourceArray = new JSONArray();
for (HostsSource source : sources) {
sourceArray.put(sourceToJson(source));
}
return sourceArray;
}
private static JSONArray buildListBackup(List<HostListItem> hosts) throws JSONException {
JSONArray listArray = new JSONArray();
for (HostListItem host : hosts) {
listArray.put(hostToJson(host));
}
return listArray;
}
}

View file

@ -0,0 +1,84 @@
package org.adaway.model.backup;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.HostsSource;
import org.json.JSONException;
import org.json.JSONObject;
import static org.adaway.db.entity.HostsSource.USER_SOURCE_ID;
/**
* This class defines user lists and hosts sources file format.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
final class BackupFormat {
/*
* Source backup format.
*/
static final String SOURCES_KEY = "sources";
static final String SOURCE_LABEL_ATTRIBUTE = "label";
static final String SOURCE_URL_ATTRIBUTE = "url";
static final String SOURCE_ENABLED_ATTRIBUTE = "enabled";
static final String SOURCE_ALLOW_ATTRIBUTE = "allow";
static final String SOURCE_REDIRECT_ATTRIBUTE = "redirect";
/*
* User source backup format.
*/
static final String BLOCKED_KEY = "blocked";
static final String ALLOWED_KEY = "allowed";
static final String REDIRECTED_KEY = "redirected";
static final String ENABLED_ATTRIBUTE = "enabled";
static final String HOST_ATTRIBUTE = "host";
static final String REDIRECT_ATTRIBUTE = "redirect";
BackupFormat() {
}
static JSONObject sourceToJson(HostsSource source) throws JSONException {
JSONObject sourceObject = new JSONObject();
sourceObject.put(SOURCE_LABEL_ATTRIBUTE, source.getLabel());
sourceObject.put(SOURCE_URL_ATTRIBUTE, source.getUrl());
sourceObject.put(SOURCE_ENABLED_ATTRIBUTE, source.isEnabled());
sourceObject.put(SOURCE_ALLOW_ATTRIBUTE, source.isAllowEnabled());
sourceObject.put(SOURCE_REDIRECT_ATTRIBUTE, source.isRedirectEnabled());
return sourceObject;
}
static HostsSource sourceFromJson(JSONObject sourceObject) throws JSONException {
HostsSource source = new HostsSource();
source.setLabel(sourceObject.getString(SOURCE_LABEL_ATTRIBUTE));
String url = sourceObject.getString(SOURCE_URL_ATTRIBUTE);
if (!HostsSource.isValidUrl(url)) {
throw new JSONException("Invalid source URL: "+url);
}
source.setUrl(url);
source.setEnabled(sourceObject.getBoolean(SOURCE_ENABLED_ATTRIBUTE));
source.setAllowEnabled(sourceObject.getBoolean(SOURCE_ALLOW_ATTRIBUTE));
source.setRedirectEnabled(sourceObject.getBoolean(SOURCE_REDIRECT_ATTRIBUTE));
return source;
}
static JSONObject hostToJson(HostListItem host) throws JSONException {
JSONObject hostObject = new JSONObject();
hostObject.put(HOST_ATTRIBUTE, host.getHost());
String redirection = host.getRedirection();
if (redirection != null && !redirection.isEmpty()) {
hostObject.put(REDIRECT_ATTRIBUTE, redirection);
}
hostObject.put(ENABLED_ATTRIBUTE, host.isEnabled());
return hostObject;
}
static HostListItem hostFromJson(JSONObject hostObject) throws JSONException {
HostListItem host = new HostListItem();
host.setHost(hostObject.getString(HOST_ATTRIBUTE));
if (hostObject.has(REDIRECT_ATTRIBUTE)) {
host.setRedirection(hostObject.getString(REDIRECT_ATTRIBUTE));
}
host.setEnabled(hostObject.getBoolean(ENABLED_ATTRIBUTE));
host.setSourceId(USER_SOURCE_ID);
return host;
}
}

View file

@ -0,0 +1,136 @@
package org.adaway.model.backup;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.UiThread;
import org.adaway.R;
import org.adaway.db.AppDatabase;
import org.adaway.db.dao.HostListItemDao;
import org.adaway.db.dao.HostsSourceDao;
import org.adaway.db.entity.HostListItem;
import org.adaway.db.entity.ListType;
import org.adaway.util.AppExecutors;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Optional;
import java.util.concurrent.Executor;
import static org.adaway.db.entity.ListType.ALLOWED;
import static org.adaway.db.entity.ListType.BLOCKED;
import static org.adaway.db.entity.ListType.REDIRECTED;
import static org.adaway.model.backup.BackupFormat.ALLOWED_KEY;
import static org.adaway.model.backup.BackupFormat.BLOCKED_KEY;
import static org.adaway.model.backup.BackupFormat.REDIRECTED_KEY;
import static org.adaway.model.backup.BackupFormat.SOURCES_KEY;
import static org.adaway.model.backup.BackupFormat.hostFromJson;
import static org.adaway.model.backup.BackupFormat.sourceFromJson;
import timber.log.Timber;
/**
* This class is a helper class to import user lists and hosts sources to a backup file.<br>
* Importing a file source will no restore read access from Storage Access Framework.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public final class BackupImporter {
private BackupImporter() {
}
private static final Executor DISK_IO_EXECUTOR = AppExecutors.getInstance().diskIO();
private static final Executor MAIN_THREAD_EXECUTOR = AppExecutors.getInstance().mainThread();
/**
* Import a backup file.
*
* @param context The application context.
* @param backupUri The URI of a backup file.
*/
@UiThread
public static void importFromBackup(Context context, Uri backupUri) {
DISK_IO_EXECUTOR.execute(() -> {
boolean imported = true;
try {
importBackup(context, backupUri);
} catch (IOException e) {
Timber.e(e, "Failed to import backup.");
imported = false;
}
boolean successful = imported;
MAIN_THREAD_EXECUTOR.execute(() -> notifyImportEnd(context, successful));
});
}
@UiThread
private static void notifyImportEnd(Context context, boolean successful) {
Toast.makeText(
context,
context.getString(successful ? R.string.import_success : R.string.import_failed),
Toast.LENGTH_LONG
).show();
}
static void importBackup(Context context, Uri backupUri) throws IOException {
try (InputStream inputStream = context.getContentResolver().openInputStream(backupUri);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder contentBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
contentBuilder.append(line);
}
JSONObject backupObject = new JSONObject(contentBuilder.toString());
importBackup(context, backupObject);
} catch (JSONException exception) {
throw new IOException("Failed to parse backup file.", exception);
} catch (FileNotFoundException exception) {
throw new IOException("Failed to find backup file.", exception);
} catch (IOException exception) {
throw new IOException("Failed to read backup file.", exception);
}
}
private static void importBackup(Context context, JSONObject backupObject) throws JSONException {
AppDatabase database = AppDatabase.getInstance(context);
HostsSourceDao hostsSourceDao = database.hostsSourceDao();
HostListItemDao hostListItemDao = database.hostsListItemDao();
importSourceBackup(hostsSourceDao, backupObject.getJSONArray(SOURCES_KEY));
importListBackup(hostListItemDao, BLOCKED, backupObject.getJSONArray(BLOCKED_KEY));
importListBackup(hostListItemDao, ALLOWED, backupObject.getJSONArray(ALLOWED_KEY));
importListBackup(hostListItemDao, REDIRECTED, backupObject.getJSONArray(REDIRECTED_KEY));
}
private static void importSourceBackup(HostsSourceDao hostsSourceDao, JSONArray sources) throws JSONException {
for (int index = 0; index < sources.length(); index++) {
JSONObject sourceObject = sources.getJSONObject(index);
hostsSourceDao.insert(sourceFromJson(sourceObject));
}
}
private static void importListBackup(HostListItemDao hostListItemDao, ListType type, JSONArray hosts) throws JSONException {
for (int index = 0; index < hosts.length(); index++) {
JSONObject hostObject = hosts.getJSONObject(index);
HostListItem host = hostFromJson(hostObject);
host.setType(type);
Optional<Integer> id = hostListItemDao.getHostId(host.getHost());
if (id.isPresent()) {
host.setId(id.get());
hostListItemDao.update(host);
} else {
hostListItemDao.insert(host);
}
}
}
}

View file

@ -0,0 +1,44 @@
package org.adaway.model.error;
import androidx.annotation.StringRes;
import org.adaway.R;
/**
* This enumeration represents the hosts error case.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public enum HostError {
// Source model errors
NO_CONNECTION(R.string.error_no_connection_message, R.string.error_no_connection_details),
DOWNLOAD_FAILED(R.string.error_download_failed_message, R.string.error_no_connection_details),
// Host install model errors
PRIVATE_FILE_FAILED(R.string.error_private_file_failed_message, R.string.error_private_file_failed_details),
NOT_ENOUGH_SPACE(R.string.error_not_enough_space_message, R.string.error_not_enough_space_details),
COPY_FAIL(R.string.error_copy_failed_message, R.string.error_copy_failed_details),
REVERT_FAIL(R.string.error_revert_failed_message, R.string.error_revert_failed_details),
// VPN model error
ENABLE_VPN_FAIL(R.string.error_enable_vpn_failed_message, R.string.error_enable_vpn_failed_details),
DISABLE_VPN_FAIL(R.string.error_disable_vpn_failed_message, R.string.error_disable_vpn_failed_details);
@StringRes
private final int messageKey;
@StringRes
private final int detailsKey;
HostError(int messageKey, int detailsKey) {
this.messageKey = messageKey;
this.detailsKey = detailsKey;
}
@StringRes
public int getMessageKey() {
return this.messageKey;
}
@StringRes
public int getDetailsKey() {
return this.detailsKey;
}
}

View file

@ -0,0 +1,43 @@
package org.adaway.model.error;
/**
* This class in an {@link Exception} thrown on hosts error.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class HostErrorException extends Exception {
/**
* The exception error type.
*/
private final HostError error;
/**
* Constructor.
*
* @param error The exception error type.
*/
public HostErrorException(HostError error) {
super("An host error " + error.name() + " occurred");
this.error = error;
}
/**
* Constructor.
*
* @param error The exception error type.
* @param cause The cause of this exception.
*/
public HostErrorException(HostError error, Throwable cause) {
super("An host error " + error.name() + " occurred", cause);
this.error = error;
}
/**
* Get the error type.
*
* @return The exception error type
*/
public HostError getError() {
return this.error;
}
}

View file

@ -0,0 +1,61 @@
package org.adaway.model.git;
import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import timber.log.Timber;
/**
* This class is an utility class to get information from GitHub gist hosting.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
class GistHostsSource extends GitHostsJsonApiSource {
/**
* The gist identifier.
*/
private final String gistIdentifier;
/**
* Constructor.
*
* @param url The hosts file URL hosted on GitHub gist.
* @throws MalformedURLException If the URl is not a gist URL.
*/
GistHostsSource(String url) throws MalformedURLException {
// Check URL path
URL parsedUrl = new URL(url);
String path = parsedUrl.getPath();
String[] pathParts = path.split("/");
if (pathParts.length < 2) {
throw new MalformedURLException("The GitHub gist URL " + url + " is not valid.");
}
// Extract gist identifier from path
this.gistIdentifier = pathParts[2];
}
@Override
protected String getCommitApiUrl() {
return "https://api.github.com/gists/" + this.gistIdentifier;
}
@Nullable
protected ZonedDateTime parseJsonBody(String body) throws JSONException {
JSONObject gistObject = new JSONObject(body);
String dateString = gistObject.getString("updated_at");
ZonedDateTime date = null;
try {
date = ZonedDateTime.parse(dateString);
} catch (DateTimeParseException exception) {
Timber.w(exception, "Failed to parse commit date: " + dateString + ".");
}
return date;
}
}

View file

@ -0,0 +1,53 @@
package org.adaway.model.git;
import androidx.annotation.Nullable;
import org.json.JSONException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.time.ZonedDateTime;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
/**
* This class is an utility class to get information from Git hosted hosts sources from JSON API.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public abstract class GitHostsJsonApiSource extends GitHostsSource {
@Override
@Nullable
public ZonedDateTime getLastUpdate() {
return getLastUpdateFromApi(getCommitApiUrl());
}
protected abstract String getCommitApiUrl();
@Nullable
protected ZonedDateTime getLastUpdateFromApi(String commitApiUrl) {
// Create client and request
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(commitApiUrl).build();
try (Response response = client.newCall(request).execute();
ResponseBody body = response.body()) {
if (response.isSuccessful()) {
return parseJsonBody(body.string());
}
} catch (UnknownHostException | SocketTimeoutException exception) {
Timber.i(exception, "Unable to reach API backend.");
} catch (IOException | JSONException exception) {
Timber.e(exception, "Unable to get commits from API.");
}
// Return failed
return null;
}
@Nullable
protected abstract ZonedDateTime parseJsonBody(String body) throws JSONException;
}

View file

@ -0,0 +1,65 @@
package org.adaway.model.git;
import androidx.annotation.Nullable;
import java.net.MalformedURLException;
import java.time.ZonedDateTime;
/**
* This class is an utility class to get information from Git hosted hosts sources.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public abstract class GitHostsSource {
/**
* The GitHub repository URL.
*/
private static final String GITHUB_REPO_URL = "https://raw.githubusercontent.com/";
/**
* The GitHub gist URL.
*/
private static final String GITHUB_GIST_URL = "https://gist.githubusercontent.com";
/**
* The GitLab URL.
*/
private static final String GITLAB_URL = "https://gitlab.com/";
/**
* Check if a hosts file url is hosted on Git hosting.
*
* @param url The url to check.
* @return {@code true} if the hosts file is hosted on Git hosting, {@code false} otherwise.
*/
public static boolean isHostedOnGit(String url) {
return url.startsWith(GITHUB_REPO_URL) ||
url.startsWith(GITHUB_GIST_URL) ||
url.startsWith(GITLAB_URL);
}
/**
* Get the GitHub hosts source.
*
* @param url The URL to get source from.
* @return The GitHub hosts source.
* @throws MalformedURLException If the URL is not a GitHub URL or not a supported GitHub URL.
*/
public static GitHostsSource getSource(String url) throws MalformedURLException {
if (url.startsWith(GITHUB_REPO_URL)) {
return new GitHubHostsSource(url);
} else if (url.startsWith(GITHUB_GIST_URL)) {
return new GistHostsSource(url);
} else if (url.startsWith(GITLAB_URL)) {
return new GitLabHostsSource(url);
} else {
throw new MalformedURLException("URL is not a supported Git hosting URL");
}
}
/**
* Get last update of the hosts file.
*
* @return The last update date, {@code null} if the date could not be retrieved.
*/
@Nullable
public abstract ZonedDateTime getLastUpdate();
}

View file

@ -0,0 +1,84 @@
package org.adaway.model.git;
import static java.util.stream.Collectors.joining;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import timber.log.Timber;
/**
* This class is an utility class to get information from GitHub repository hosting.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
class GitHubHostsSource extends GitHostsJsonApiSource {
/**
* The GitHub owner name.
*/
private final String owner;
/**
* The GitHub repository name.
*/
private final String repo;
/**
* The GitHub blob (hosts file) path.
*/
private final String blobPath;
/**
* Constructor.
*
* @param url The hosts file URL hosted on GitHub.
* @throws MalformedURLException If the URl is not a GitHub URL.
*/
GitHubHostsSource(String url) throws MalformedURLException {
// Check URL path
URL parsedUrl = new URL(url);
String path = parsedUrl.getPath();
String[] pathParts = path.split("/");
if (pathParts.length < 5) {
throw new MalformedURLException("The GitHub user content URL " + url + " is not valid.");
}
// Extract components from path
this.owner = pathParts[1];
this.repo = pathParts[2];
this.blobPath = Arrays.stream(pathParts)
.skip(4)
.collect(joining("/"));
}
@Override
protected String getCommitApiUrl() {
return "https://api.github.com/repos/" + this.owner + "/" + this.repo +
"/commits?per_page=1&path=" + this.blobPath;
}
@Nullable
protected ZonedDateTime parseJsonBody(String body) throws JSONException {
JSONArray commitArray = new JSONArray(body);
int nbrOfCommits = commitArray.length();
ZonedDateTime date = null;
for (int i = 0; i < nbrOfCommits && date == null; i++) {
JSONObject commitItemObject = commitArray.getJSONObject(i);
JSONObject commitObject = commitItemObject.getJSONObject("commit");
JSONObject committerObject = commitObject.getJSONObject("committer");
String dateString = committerObject.getString("date");
try {
date = ZonedDateTime.parse(dateString);
} catch (DateTimeParseException exception) {
Timber.w(exception, "Failed to parse commit date: " + dateString + ".");
}
}
return date;
}
}

View file

@ -0,0 +1,81 @@
package org.adaway.model.git;
import static java.util.stream.Collectors.joining;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import timber.log.Timber;
/**
* This class is an utility class to get information from GitLab hosts source hosting.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class GitLabHostsSource extends GitHostsJsonApiSource {
/**
* The GitHub owner name.
*/
private final String owner;
/**
* The GitHub repository name.
*/
private final String repo;
/**
* The GitLab reference name.
*/
private final String ref;
/**
* The GitLab (hosts) file path.
*/
private final String path;
GitLabHostsSource(String url) throws MalformedURLException {
// Check URL path
URL parsedUrl = new URL(url);
String path = parsedUrl.getPath();
String[] pathParts = path.split("/");
if (pathParts.length < 5) {
throw new MalformedURLException("The GitLab user content URL " + url + " is not valid.");
}
// Extract components from path
this.owner = pathParts[1];
this.repo = pathParts[2];
this.ref = pathParts[4];
this.path = Arrays.stream(pathParts)
.skip(5)
.collect(joining("/"));
}
@Override
protected String getCommitApiUrl() {
return "https://gitlab.com/api/v4/projects/" + this.owner + "%2F" + this.repo
+ "/repository/commits?path=" + this.path + "&ref_name=" + this.ref;
}
@Nullable
protected ZonedDateTime parseJsonBody(String body) throws JSONException {
JSONArray commitArray = new JSONArray(body);
int nbrOfCommits = commitArray.length();
ZonedDateTime date = null;
for (int i = 0; i < nbrOfCommits && date == null; i++) {
JSONObject commitItemObject = commitArray.getJSONObject(i);
String dateString = commitItemObject.getString("committed_date");
try {
date = ZonedDateTime.parse(dateString);
} catch (DateTimeParseException exception) {
Timber.w(exception, "Failed to parse commit date: " + dateString + ".");
}
}
return date;
}
}

Some files were not shown because too many files have changed in this diff Show more