313 Commits

Autor SHA1 Mensagem Data
Anthony Restaino 8061d8726a Add clear button to the search bar instead of go button 2015-08-30 15:23:59 -04:00
Anthony Restaino 1896fa6151 Animations for activity transitions 2015-08-27 22:44:22 -04:00
Anthony Restaino 98f0daceaa method could be static 2015-08-25 21:02:37 -04:00
Anthony Restaino 367c62bd39 Improved reading mode thanks to changes from snacktory fork by skyshard 2015-08-25 20:59:23 -04:00
Anthony Restaino 04c9f75a90 Added option for empty user agent if the user sets an empty string to work around webview limitations 2015-08-25 20:19:38 -04:00
Anthony Restaino dd18526ddf Fixed some deprecation problems and code analysis warnings 2015-08-23 23:26:21 -04:00
Anthony Restaino 85d92db738 Switched to RecyclerView, cleaned up some HTML generator methods 2015-08-23 19:21:22 -04:00
Anthony Restaino b68ad65abc Added permission handling and support for API 23 2015-08-23 12:13:06 -04:00
Anthony Restaino a0ade8acc9 Changed padding on toolbar for consistency, updated build tools 2015-08-22 10:04:35 -04:00
Anthony Restaino 676ba822af Try to fix problem with netcipher library 2015-08-22 09:24:58 -04:00
Anthony Restaino 1e385ceb9a Added back sdk 22 necessary for orbot library 2015-08-22 09:11:18 -04:00
Anthony Restaino 9f2f9d74eb First step toward Android M support, compile with sdk 23, fix errors caused by upgrade
removed copy button from search bar and replaced with go action. Had to
remove browser content provider usage as it is not longer included in
the sdk and has been completely removed.
2015-08-22 09:08:39 -04:00
Anthony Restaino 68f5c4fb45 Better URL validation, thanks AOSP 2015-08-21 21:55:55 -04:00
Anthony Restaino a08d793320 Added homepage button, altered tab UI slightly, fixed URL validation 2015-08-21 21:33:45 -04:00
Anthony Restaino d8fc799586 Merge branch 'master' into dev 2015-08-21 17:59:28 -04:00
Anthony Restaino f3b0e46801 Fixed many code analysis warnings 2015-08-21 17:55:58 -04:00
Anthony Restaino d5e1e06d84 Fixed bug where history wasn't being deleted until the app was restarted 2015-08-21 17:15:08 -04:00
Anthony Restaino 119245a5fa removed unused strings 2015-08-21 17:07:03 -04:00
Anthony Restaino ff5810c89a another attempt to fix travis 2015-08-21 16:57:19 -04:00
Anthony Restaino 2aa03a87a6 added info about contributing 2015-08-21 16:47:08 -04:00
Anthony Restaino d6fbfeaf29 Attempt to fix continuous integration build error 2015-08-21 16:43:20 -04:00
Anthony Restaino 88f07e3ced Update travis CI settings to support containers for faster building 2015-08-21 16:14:14 -04:00
Anthony Restaino c301f3963a Fixed a couple code warnings 2015-08-21 16:05:10 -04:00
Anthony Restaino 0a67f9e92a Renamed folders to fix build error 2015-08-21 16:04:23 -04:00
Anthony Restaino c9579b9d82 Merge branch 'master' into dev
Conflicts:
	app/src/main/res/values-it/strings.xml
2015-08-21 15:54:13 -04:00
Anthony Restaino f39631bd23 Merge pull request #283 from yuki2006/dev
fix: about scheme
2015-08-20 21:00:53 -04:00
Anthony Restaino f963b4de7f Merge pull request #282 from MarkThat/patch-4
Same as branch master, every string is updated.
2015-08-20 21:00:22 -04:00
Anthony Restaino 6be3cab470 Merge pull request #281 from MarkThat/patch-1
Changed a few old strings and added new.
2015-08-20 21:00:05 -04:00
Anthony Restaino b619a12ae3 Miscellaneous code analysis warning fixes 2015-08-20 20:59:24 -04:00
Anthony Restaino 58c9e820ed Initial support for tabs on the top instead of in the navigation drawer
added a setting to switch between modes. Still needs work to be less
buggy
2015-08-20 20:58:33 -04:00
ono 33eb739824 fix: about scheme 2015-08-20 14:23:20 +09:00
Mark. 81cef51479 Same as branch master, every string is updated. 2015-08-19 12:35:09 +02:00
Mark. 839616a4e4 Changed a few old strings and added new. 2015-08-18 19:05:40 +02:00
Anthony Restaino e71e09c2e8 Further generify Adblock host loading 2015-08-17 19:23:09 -04:00
Anthony Restaino 25a80a86a5 Update hosts file, create versatile hosts loading method to make way for users to load from any hosts file 2015-08-16 17:14:48 -04:00
Anthony Restaino 59c720d7d8 Fixed a setting, lowered priority on a thread 2015-08-12 21:01:52 -04:00
Anthony Restaino 7e67770617 Implement javascript close window method in the browser 2015-08-11 19:59:01 -04:00
Anthony Restaino c4e244a82b Make incognito mode safer, fix crash in search adapter, 2015-08-10 20:57:01 -04:00
Anthony Restaino 29a20a7e58 Updated with latest changes from NetCipher library 2015-08-06 19:21:00 -04:00
Anthony Restaino a738308a50 Added korean translation 2015-08-05 22:49:42 -04:00
Anthony Restaino 5081ee2ea6 Fix RuntimeException on Android M Preview 2015-08-05 22:10:51 -04:00
Anthony Restaino 29d2a5f3e5 Use single WebkitProxy reset method 2015-08-05 21:34:02 -04:00
Anthony Restaino c39a835dc2 Update netcipher library to latest version 2015-08-05 20:06:19 -04:00
Anthony Restaino 4ba7c7c5a3 Fixed some bugs 2015-08-05 20:04:28 -04:00
Anthony Restaino 08eedbe121 Add option to clear Web Storage 2015-08-04 20:08:55 -04:00
Anthony Restaino 4a32f0dfba Merge pull request #271 from anthologist/master
Updated italian translation
2015-08-04 20:08:45 -04:00
anthologist f101ea34ce Update strings.xml 2015-08-04 16:35:23 +02:00
anthologist 9f036410d2 Update strings.xml 2015-08-04 12:14:21 +02:00
anthologist 86834fca60 Update strings.xml 2015-08-04 12:11:25 +02:00
Anthony Restaino 3b13999b03 Added text encoding setting, updated support libraries 2015-08-03 22:33:11 -04:00
anthologist cc78b4196f Update strings.xml 2015-08-03 19:23:17 +02:00
Anthony Restaino b8b610347f fixed full-screen mode when watching a video in full-screen 2015-08-02 11:42:18 -04:00
Anthony Restaino 24a99deb52 Add suggestions to naming a folder in edit bookmark dialog 2015-07-31 21:37:26 -04:00
Anthony Restaino 0f9a69ba17 Updated hosts file 2015-07-30 20:26:43 -04:00
Anthony Restaino 71fcd174d7 fixed bug in search adapter 2015-07-29 22:46:59 -04:00
Anthony Restaino 399037d49b Fixed bug when long pressing bookmarks, removed unused strings 2015-07-29 22:01:01 -04:00
Anthony Restaino 240c9a5a37 Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	app/src/main/res/values-fr/strings.xml
2015-07-29 21:44:33 -04:00
Anthony Restaino 4a3362e8f1 Merge pull request #268 from David-Guillot/patch-1
French translations, first batch
2015-07-29 21:38:38 -04:00
Anthony Restaino 7331345348 More clear and understandable suggestions filtering algorithm 2015-07-29 21:38:23 -04:00
Anthony Restaino 73e8f7c314 long-press on a folder on the bookmarks page works correctly now + other cleanup 2015-07-27 22:50:14 -04:00
Anthony Restaino aced4a3cc7 Sort bookmarks so folders are at the end of the list, updated bookmarks page to better utilize space 2015-07-26 20:19:47 -04:00
Anthony Restaino 69deb5b5a2 Renaming and Deleting bookmark folders is now available yay 2015-07-26 13:49:45 -04:00
David Guillot 3a76439a65 Fixed forgotten escaping 2015-07-26 14:41:47 +02:00
David Guillot fa7b04adee Fixed typo
Thanks @kuc
2015-07-26 14:36:32 +02:00
David Guillot a708050c5b French translations, first batch 2015-07-26 13:11:40 +02:00
Anthony Restaino 645b98cd50 Simplify and improve long press handling for links on the bookmark page and history page 2015-07-25 22:05:09 -04:00
Anthony Restaino 19103e9b2c Added Bookmark folders, Added actions to the bookmark drawer, + other
Updated icons, removed light/dark versions only have one version now
that uses a color filter to be themed to save space, optimized view
layouts
2015-07-25 10:19:14 -04:00
Anthony Restaino dce29954e1 Down with Toast, all hail Snackbar 2015-07-19 16:58:34 -04:00
Anthony Restaino f061a35472 Snackbar >>>>>>> Toast 2015-07-19 16:49:55 -04:00
Anthony Restaino 800d037035 Removed use of tabs and replaced with 4 spaces 2015-07-19 15:42:14 -04:00
Anthony Restaino e35b368d50 Updated ProxyUtils to automatically start TOR when needed, more abstraction of BrowserActivity, other cleanup 2015-07-19 15:36:41 -04:00
Anthony Restaino aa21657875 Fixed errors with ProxyUtils 2015-07-18 17:59:43 -04:00
Anthony Restaino f314b64e40 updated to latest netcipher submodule 2015-07-18 16:59:57 -04:00
Anthony Restaino 6c9d23488b Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	app/src/main/res/values-pt/strings.xml
2015-07-18 16:43:42 -04:00
Anthony Restaino 41cb2c4d27 Convert BrowserActivity to an abstract class, remove unused resources 2015-07-18 16:38:57 -04:00
Anthony Restaino 969cab81e7 New Full-screen mode works better and doesn't hide the top of the WebView, +other
* Now using material alertdialog on all versions
* cleaned up some code
* fixed lint issues and other inspection related problems
* Attempted to fix bugs found
2015-07-18 14:30:41 -04:00
Anthony Restaino b98dd272c0 Merge pull request #255 from DF1E/settings
Material Settings
2015-07-18 10:05:35 -04:00
Anthony Restaino 67b506c5f8 Merge pull request #254 from smarquespt/master
Portuguese update
2015-07-17 22:18:09 -04:00
Anthony Restaino e9af39a20b Merge pull request #260 from chirs1985/master
1. Use the " \ " character to escape the " ' " character
2015-07-17 22:16:04 -04:00
chirs1985 5950c66567 1. Use the " \ " character to escape the " ' " character
2. lose the </string-arrary>
2015-07-06 11:06:39 +08:00
DF1E 56c9934145 remove LicenseActivity intent 2015-06-16 15:13:27 +02:00
DF1E 1d5b904c64 create PreferenceCategory for licenses 2015-06-15 18:39:24 +02:00
DF1E 7d9f382333 Toolbar fix 2015-06-14 12:13:35 +02:00
DF1E c60b4389a0 small fix
this info dialog is not necessary because the flash CheckBox isn't
enabled if API>19
2015-06-13 19:37:07 +02:00
DF1E 6b80df1d27 clean manifest 2015-06-12 17:10:46 +02:00
DF1E b60f555553 new Settings 3/3
now we have to bring the ToolBar back and fix some bugs...
And testing!
2015-06-12 17:10:23 +02:00
DF1E 2127863465 new Settings 2/3 2015-06-12 14:00:36 +02:00
DF1E d47a86d9b5 new Settings 1/3 2015-06-12 13:14:29 +02:00
DF1E a03444f4d0 rebase test for main settings screen 2015-06-11 18:48:24 +02:00
Sérgio Marques 86b8eaee12 Portuguese update 2015-06-09 10:21:35 +01:00
Anthony Restaino d8b8d2c047 Moved proxy code to utility class, remove proxy from lite version to reduce apk size 2015-06-05 00:03:02 -04:00
Anthony Restaino 9dc9634299 Fixed bug 2015-06-03 11:00:54 -04:00
Anthony Restaino e13e2dd006 Fix error in strings 2015-06-03 10:58:54 -04:00
Anthony Restaino 4c7bb196dd Merge remote-tracking branch 'origin/master' into dev 2015-06-03 10:55:53 -04:00
Anthony Restaino a2f2fbc82b Merge pull request #249 from str4d/i2p-android
Support I2P (and other proxies in future)
2015-06-03 10:54:11 -04:00
Anthony Restaino 80d765b61c Merge remote-tracking branch 'origin/master' into dev 2015-06-03 10:52:34 -04:00
Anthony Restaino b478c1ea98 Code cleanup 2015-06-03 10:50:51 -04:00
Anthony Restaino 7e5dbbc811 Merge pull request #251 from MarkThat/patch-3
Patch 3
2015-06-03 10:49:00 -04:00
str4d 2eec8be4ce Add manual proxy picker 2015-06-03 06:50:32 +00:00
Mark. 0c10efec00 Corrections.
Removed few strings which i added for error. Can these changes be merged without gradle?  I don't understand how to use it hehe.
Cheers!
2015-05-30 11:39:41 +02:00
Mark. 2cbe2e80e7 Update strings.xml 2015-05-29 15:36:05 +02:00
Mark. e7029a7b64 Update.
I found some errors and fixed them and added the new translations.
2015-05-29 15:34:19 +02:00
Anthony Restaino 1e0770057e Merge pull request #241 from AltNico/cleanup
Remove unnecessary files
2015-05-27 07:21:13 -04:00
str4d 810483ec74 Use constants for proxy choices, part 2 2015-05-26 12:52:11 +00:00
str4d a0b2197d8f Use constants for proxy choices 2015-05-26 10:52:58 +00:00
str4d a5a20eebbd Notify user if proxy is not ready when they try to load a URL 2015-05-26 10:36:02 +00:00
str4d 6e8da9f6d3 Fix for I2PAndroidHelper.isI2PAndroidRunning() always returning false
Requires v0.7 of the I2P client library, which will be released once I2P 0.9.20
is released.
2015-05-26 03:25:47 +00:00
Nico Alt c74b84d070 remove unnecessary files
Basically, all I have done was adding some missing files/directories to
.gitignore and the executing the following commands:
git rm --cached -r ./
git add --all
2015-05-25 22:43:24 +02:00
Anthony Restaino e9203f20b3 Utilize gradle product flavors to produce free and plus versions 2015-05-25 12:55:35 -04:00
Anthony Restaino 4a38511218 Merge remote-tracking branch 'origin/master' into dev 2015-05-25 11:53:21 -04:00
Anthony Restaino 3517435956 Merge pull request #247 from Roboe/l10n-es
Updated Spanish (es) localization
2015-05-25 11:51:08 -04:00
Anthony Restaino 8bb38d1a90 Merge pull request #238 from ageback/master
Complete S.Chinese.
2015-05-25 11:45:18 -04:00
str4d 111d594c6b Use I2P if configured 2015-05-25 12:09:36 +00:00
str4d 6c2a557135 Change Orbot checkbox to an HTTP proxy choice list (None, Orbot, I2P) 2015-05-24 13:20:08 +00:00
str4d 46fbc56604 Add I2P client library to dependencies 2015-05-24 13:19:02 +00:00
Roboe 0beec60b7f [Spanish l10n] Fixed duplicated string 2015-05-24 00:19:40 +02:00
Roboe 1de67105d9 [Spanish l10n] Added new strings 2015-05-23 17:37:45 +02:00
Roberto M.F. eacadcebba [Spanish l10n] Fixed some strings and updated untranslated ones 2015-05-22 18:09:49 +02:00
Anthony Restaino 9f8dff8c5d Added an AMOLED Black theme, changed from tabs to spaces for some files 2015-05-13 10:35:32 -04:00
Anthony Restaino b400ef0647 Merge pull request #237 from smarquespt/master
Portuguese update
2015-05-13 09:09:55 -04:00
Michael Lu 025714bd97 Complete S.Chinese. 2015-05-13 15:26:03 +08:00
Sérgio Marques 5e131a0a6c Portuguese update 2015-05-12 16:34:22 +01:00
Anthony Restaino 9677135c28 Merge pull request #234 from anthonycr/dev
compile jsoup from jcenter instead of embedding jar in code
2015-05-11 11:08:38 -04:00
Anthony Restaino a0268a9dfa compile jsoup from jcenter instead of embedding jar in code 2015-05-11 10:06:00 -04:00
Anthony Restaino 28498d7d92 Merge pull request #232 from anthonycr/dev
Dev
2015-05-10 17:03:57 -04:00
Anthony Restaino 2f3655045c Updated to latest gradle plugin 2015-05-10 16:35:21 -04:00
Anthony Restaino 459c5f8ff0 updated to latest netcipher library 2015-05-10 16:27:19 -04:00
Anthony Restaino e335a2b936 Remove unused resources, code cleanup 2015-05-05 20:57:42 -04:00
Anthony Restaino 051a453e7b Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	app/src/main/res/values-sr/strings.xml
2015-05-04 12:30:17 -04:00
Anthony Restaino df903551c0 Merge branch 'pr/229' into dev
Conflicts:
	app/build.gradle
2015-05-04 12:23:51 -04:00
Anthony Restaino 99a23c2eef Remove unused resources on build 2015-05-04 12:07:15 -04:00
Anthony Restaino fa1994c8b2 Lint fixes and code cleanup 2015-05-04 12:06:51 -04:00
DF1E f5fcc2e62b improvements for android studio 2015-05-04 18:00:39 +02:00
Anthony Restaino cb27bf8afa Fix BrowserApp class not being found by webkitproxy 2015-05-03 00:11:46 -04:00
Anthony Restaino 51fc8d7f85 Fix missing strings error, remove unused icon 2015-05-02 23:40:47 -04:00
Anthony Restaino 13d85c0f90 Serbian translation thanks to @pekjam 2015-05-02 23:11:46 -04:00
Anthony Restaino 15fcb2ed62 Merge pull request #227 from pejakm/srupd
Update Serbian translation
2015-05-02 23:09:53 -04:00
Mladen Pejaković e6174e82d2 Update Serbian translation 2015-05-03 01:16:09 +02:00
Anthony Restaino f8c2d0096d Move java files to sub-packages for better organization 2015-05-02 16:37:22 -04:00
Anthony Restaino db734dfa7d Exclude support jars from netcipher build 2015-05-02 14:57:12 -04:00
Anthony Restaino e40f1d44a5 add m2repository 2015-05-02 13:25:27 -04:00
Anthony Restaino e7188c5985 Update build to search for latest support repos 2015-05-02 13:12:16 -04:00
Anthony Restaino 8738af72f0 Update to latest sdk 2015-05-02 13:05:32 -04:00
Anthony Restaino b02f726a32 Fix travis yml 2015-05-02 13:00:17 -04:00
Anthony Restaino 4868cf0cc6 Try to fix line end bug 2015-05-02 12:56:53 -04:00
Anthony Restaino fa77fe228a Dummy change to gradleq 2015-05-02 12:56:23 -04:00
Anthony Restaino e65e062652 Fixing travis 2015-05-02 12:51:14 -04:00
Anthony Restaino 27513bd94e Add in appropriate .idea files 2015-05-02 12:47:06 -04:00
Anthony Restaino d39bf65880 Remove unnecessary iml file 2015-05-02 12:44:09 -04:00
Anthony Restaino 8e32668305 Remove old iml file 2015-05-02 12:42:27 -04:00
Anthony Restaino ce3923d336 Switch to gradle!!! 2015-05-02 12:40:40 -04:00
Anthony Restaino 38bce6a9a0 Merge pull request #222 from anthonycr/dev
Dev -> master
2015-05-01 09:39:31 -04:00
Anthony Restaino 0c88fbaec8 Merge branch 'dev' of https://github.com/anthonycr/Lightning-Browser into dev 2015-05-01 09:31:46 -04:00
Anthony Restaino 5c1b765616 Clear webdata when the cookies are cleared 2015-05-01 08:58:11 -04:00
Anthony Restaino 79d32127f4 Merge pull request #215 from bebolint98/patch-1
Update strings.xml
2015-05-01 08:54:57 -04:00
Anthony Restaino e31f533bbb Merge pull request #214 from bebolint98/patch-2
Update strings.xml
2015-05-01 08:54:52 -04:00
Anthony Restaino 89515e4420 Merge pull request #217 from smarquespt/master
Add Portuguese language
2015-05-01 08:52:58 -04:00
bebolint98 35e57fa1a6 Update strings.xml 2015-04-29 19:56:49 +02:00
bebolint98 7789fc7480 Update strings.xml 2015-04-29 19:54:39 +02:00
bebolint98 0d12624cbb Update strings.xml 2015-04-29 15:38:47 +02:00
bebolint98 837dd64c2c Update strings.xml 2015-04-29 15:33:51 +02:00
Sérgio Marques d3ab62dce6 Add Portuguese language 2015-04-29 12:35:45 +01:00
Anthony Restaino 4fb1a50f03 Use boolean constant to make free/plus builds easier to generate 2015-04-28 19:17:48 -04:00
Anthony Restaino 2b59ea1906 Fixed bug that crashed dialogs 2015-04-28 18:57:10 -04:00
bebolint98 419e36ffd1 Update strings.xml 2015-04-28 17:55:17 +02:00
bebolint98 37afaf7035 Update strings.xml 2015-04-28 17:39:09 +02:00
bebolint98 b552dc116f Update strings.xml 2015-04-28 17:37:18 +02:00
bebolint98 128ebfab14 Update strings.xml 2015-04-28 17:35:09 +02:00
bebolint98 9ebd876f27 Update strings.xml 2015-04-28 17:19:15 +02:00
bebolint98 03fe3cdee5 Update strings.xml 2015-04-28 17:14:19 +02:00
Anthony Restaino e042830e17 Fixed bug where incognito tabs are rememebered 2015-04-27 21:01:37 -04:00
Anthony Restaino 4711fa696a Workaround for a bug in LG devices 2015-04-25 22:22:24 -04:00
Anthony Restaino f4078cce33 Merge branch 'master' of https://github.com/anthonycr/Lightning-Browser
Conflicts:
	res/values-hu/strings.xml
	res/values-it/strings.xml
	res/values-ja/strings.xml
2015-04-25 22:12:11 -04:00
Anthony Restaino 9d92a97fe2 Merge pull request #207 from astrone/patch-2
Made some extra corrections and translated one more string
2015-04-25 21:56:24 -04:00
Anthony Restaino c4c50467dd Merge pull request #208 from ys0115/patch-1
Update strings.xml
2015-04-25 21:56:11 -04:00
Anthony Restaino 792cb0d4d6 Merge pull request #210 from bebolint98/dev
Add hungarian localization to dev branch
2015-04-25 21:55:43 -04:00
bebolint98 981d263331 Update strings.xml 2015-04-25 22:58:10 +02:00
bebolint98 15c5703a3b Create strings.xml 2015-04-25 22:39:01 +02:00
Anthony Restaino a6a1baf41b Last updates for public release 4.0.8a. Updated adblocking hosts and added some default bookmarks for better UX 2015-04-25 13:53:25 -04:00
Anthony Restaino 38d44b8c2f Refactored/Cleaned up some settings activities 2015-04-25 10:45:43 -04:00
ys0115 56bd6e07fc Update strings.xml 2015-04-25 20:07:38 +09:00
astrone 6125e385df Made some corrections and translated one more string 2015-04-24 20:56:27 +02:00
Anthony Restaino 532860245d Fixed scrolling sensitivity in full-screen, fixed http auth dialog issue 2015-04-22 08:49:05 -04:00
Anthony Restaino 68a9b1de7e update submodules from netcipher 2015-04-21 14:51:06 -04:00
Anthony Restaino 97e2e8d79a Fix file uploading on Lollipop, clean up the code 2015-04-21 14:46:30 -04:00
Anthony Restaino be3a59c74c Optimize webpage builders, fixed bug in bookmark activity 2015-04-18 15:34:40 -04:00
Anthony Restaino 17a63733fe Merge pull request #203 from bebolint98/master
Add Hungarian localization
2015-04-18 13:27:08 -04:00
Anthony Restaino 3f5c08bb5a Merge pull request #204 from astrone/patch-2
Italian translation (Update)
2015-04-18 13:26:49 -04:00
Anthony Restaino 6f30103cd9 Organize PreferenceManager 2015-04-18 13:25:42 -04:00
Anthony Restaino 2d347074a6 Use a PreferenceManager to handle SharedPreferences among classes 2015-04-18 13:21:33 -04:00
Anthony Restaino bd8d2ee0b8 Spring Cleaning 2015-04-17 14:54:20 -04:00
Anthony Restaino c1baab8c9c Remove unused class 2015-04-17 08:49:55 -04:00
Anthony Restaino 4e5eac4d5b Revert "Revert "Fixed bug in dark mode where search suggestions wouldn't show up. Sped up app startup by using singleton in BookmarkManager.""
This reverts commit cc6d7c7aa9.
2015-04-17 08:47:08 -04:00
Anthony Restaino cc6d7c7aa9 Revert "Fixed bug in dark mode where search suggestions wouldn't show up. Sped up app startup by using singleton in BookmarkManager."
This reverts commit 4822996da1.
2015-04-17 08:46:37 -04:00
Anthony Restaino 4822996da1 Fixed bug in dark mode where search suggestions wouldn't show up. Sped up app startup by using singleton in BookmarkManager. 2015-04-17 08:46:16 -04:00
bebolint98 546dbe4f8c Update strings.xml 2015-04-10 18:43:57 +02:00
astrone 013011ef09 Update strings.xml 2015-04-07 12:51:35 +02:00
bebolint98 344b662619 Update strings.xml 2015-04-05 18:16:03 +02:00
bebolint98 93f8b971e7 Update strings.xml 2015-04-05 17:31:45 +02:00
bebolint98 b135e27fd9 Update strings.xml 2015-04-04 22:15:07 +02:00
bebolint98 38f3a9c9df Update strings.xml 2015-04-04 22:11:45 +02:00
bebolint98 80d019798d strings.xml 2015-04-04 20:55:04 +02:00
bebolint98 1727d0739e Delete values-hu 2015-04-04 19:18:32 +02:00
bebolint98 7ab948ce3f Create values-hu 2015-04-04 19:18:00 +02:00
Anthony Restaino 8c29cb4450 Bugfixes, code clean up 2015-04-04 11:27:34 -04:00
astrone af4fa8ed2a Few new strings added and updated a pair.
There is an error in Privacy Settings with italian translation. Here a screenshot: https://db.tt/IPw7eaf5 
I have fixed it however i could not find the new strings "Tabs" , "Reader mode" etc.. hope to see them soon!
2015-04-04 11:36:15 +02:00
ys0115 ab5a118947 Update strings.xml 2015-04-03 13:49:53 -04:00
Ivan Markin c8dec7b305 added new strings 2015-04-03 13:49:53 -04:00
Ivan Markin c6d0a1a788 Fixed Russian translation 2015-04-03 13:49:52 -04:00
Luigigimmi 3239bcefe3 Update strings.xml 2015-04-03 13:49:52 -04:00
ys0115 2790664866 Create strings.xml
Japanese
2015-04-03 13:49:51 -04:00
Anthony Restaino bad6872f69 Improved TextReflow on Kitkat and up 2015-04-03 09:14:29 -04:00
Anthony Restaino d9e888e8a9 Change Navigation Drawer layout slightly, fix dark theme for drawer 2015-04-02 21:50:59 -04:00
Anthony Restaino 5dafd6f815 Merge pull request #202 from DF1E/theme
simplify theming
2015-04-02 20:40:24 -04:00
DF1E addaa3b2b3 fix theme on BrowserActivity
there should be only one ThemableActivity but there are two dark themes
- one for settings and on for BrowserActivity
2015-04-01 22:13:46 +02:00
DF1E 2551b3dc27 improve restart 2015-04-01 21:35:16 +02:00
DF1E aedf76e3ae improve theming 2 2015-04-01 21:31:28 +02:00
DF1E 625fbb1aa9 improve theming 1 2015-04-01 21:22:42 +02:00
DF1E c75ca89775 clean BrowserActivity 2015-04-01 20:36:10 +02:00
Anthony Restaino 763524555b Added preference changes due to theme changes. 2015-03-31 11:21:07 -04:00
Anthony Restaino a4f0c010d1 Added Dark Theme to browser. Added options to Reading Mode. 2015-03-31 11:20:41 -04:00
Anthony Restaino 1d6a445d33 Fix deprecation issues, fix a couple rendering issues 2015-03-29 18:03:33 -04:00
Anthony Restaino 7defcff9b1 Update icon 2015-03-29 00:48:39 -04:00
Anthony Restaino 5944cdc5df Lint fixes, new icon, fixes for SearchAdapter showing weird on ICS 2015-03-29 00:46:24 -04:00
Anthony Restaino 5e6a654170 Add back/forward arrows for large devices, change arrow colors to light for dark theme 2015-03-29 00:26:32 -04:00
Anthony Restaino 450ba6b0fd Update project for new API 22, remove another layer of overdraw on the WebView 2015-03-28 15:39:18 -04:00
Anthony Restaino c87c57661f Remove overdraw and stop blocking DOM storage (breaks sites) in incognito mode 2015-03-27 20:55:48 -04:00
Anthony Restaino 4699b583f0 Display shadow behind progress bar for non Lollipop devices 2015-03-26 18:46:38 -04:00
Anthony Restaino 9ff1614a0f Cache favicons when they are downloaded by the WebView for use by bookmarks 2015-03-26 18:46:14 -04:00
Anthony Restaino 58ca7fa303 Display back and forward buttons on tablets 2015-03-26 18:45:38 -04:00
Anthony Restaino 1f1ed20a7e Cache search suggestions temporarily so that repeated searches to not waste data requests 2015-03-26 12:37:29 -04:00
Anthony Restaino 8b3da70d92 Rename HistoryDatabase and convert it to a singleton for easier usage. Improved database structure. 2015-03-26 11:09:09 -04:00
Anthony Restaino f2f6f2761c Reduce overdraw on dropdown view 2015-03-25 21:56:52 -04:00
Anthony Restaino ecdf533188 Color mode dynamically lightens colors that are too dark to see 2015-03-25 20:51:29 -04:00
Anthony Restaino 0a4f650869 Switch out "Android Search" for better known Ask.com search 2015-03-24 20:45:34 -04:00
Anthony Restaino 32f4a457bb Fixed bug where progress bar didn't fade out always 2015-03-24 20:43:25 -04:00
Anthony Restaino 0116481022 Using a smoother progress bar 2015-03-24 16:13:08 -04:00
Anthony Restaino dfb0febbe7 Merge pull request #197 from ys0115/master
Add Japanese localization
2015-03-23 18:08:15 -04:00
Anthony Restaino b87eb5e90e Merge pull request #199 from DF1E/dev
complete german translation
2015-03-23 18:08:07 -04:00
Anthony Restaino 666294834a Merge pull request #196 from mark-in/ru-correct
Corrections for Russian localization
2015-03-23 18:07:56 -04:00
DF1E 3870e8c156 complete german translation 2015-03-14 11:37:10 +01:00
ys0115 86d83b887a Update strings.xml 2015-03-07 10:08:00 +09:00
Ivan Markin 4cc65d0d77 added new strings 2015-02-27 03:29:42 +03:00
Ivan Markin 095810671d Fixed Russian translation 2015-02-27 03:15:46 +03:00
Anthony Restaino 5fb00c08c2 Added in option to change URL display (url, domain, title), and other minor changes
Additional changes include removing useless code and making some utility
methods not reliant on Context
2015-02-24 13:52:17 -05:00
Anthony Restaino 88e5a0eabb Merge pull request #195 from Luigigimmi/patch-1
Update strings.xml
2015-02-23 12:58:09 -05:00
Luigigimmi 698693586c Update strings.xml 2015-02-22 11:00:09 +01:00
Anthony Restaino f1cc80eb28 Merge pull request #191 from DF1E/fix-readingactivity
fix ReadingActivity
2015-02-18 13:54:56 -05:00
ys0115 11d94564de Create strings.xml
Japanese
2015-02-14 22:47:57 +09:00
DF1E a21e2f6a7c fix ReadingActivity
I had no problems on stock android but on cyanogenmod I got crashes
without this
2015-02-12 18:28:34 +01:00
Anthony Restaino 971b0cd022 Change Reading mode package name to lower case 2015-02-09 15:46:10 -05:00
Anthony Restaino d60fe82b4a Fix lint problems and other code style problems, also fixed sluggish navigation drawer issues 2015-02-09 15:45:40 -05:00
Anthony Restaino 0c57e14f05 Add in proguard protection for Reading Mode (should keep it from crashing) 2015-02-09 15:31:16 -05:00
Anthony Restaino fa3c784722 Add in attribution to jsoup library 2015-02-09 15:30:50 -05:00
Anthony Restaino a4878914e2 Remove mdpi assets, just rely on scaled down hdpi versions 2015-02-09 15:30:11 -05:00
Anthony Restaino db20a4eeac Fixed problem where progress bar didn't display on 4.0 and 4.1 2015-02-05 20:59:52 -05:00
Anthony Restaino 10668a019b Added a Reading Mode that can be accessed from the menu
Reading Mode utilizes the Snacktory library created by karussel which is
licensed under the Apache 2.0 license.
https://github.com/karussell/snacktory
2015-02-05 15:33:23 -05:00
Anthony Restaino 313f9fb105 Fixed bug where navigation drawers sometimes overlapped 2015-02-05 12:21:16 -05:00
Anthony Restaino e7dacc9c10 Attempt to fix bug where DrawerArrowDrawable animation gets stuck half way.
Also, add a background to indicate that the exit button on a tab is
pressed.
2015-02-05 12:09:39 -05:00
Anthony Restaino 9173e8270a Merge branch 'dev' of https://github.com/anthonycr/Lightning-Browser into dev 2015-02-04 21:30:29 -05:00
Anthony Restaino ab134a8927 Fixed bug with Palette API 2015-02-04 21:30:25 -05:00
Anthony Restaino 71471f0718 Merge pull request #182 from karolba/patch-1
Update Polish translation in dev branch
2015-02-04 13:54:48 -05:00
karolba 87ee80fc8b Fix spelling 2015-02-04 17:07:45 +01:00
karolba 29d55ec890 Update Polish translation in dev branch 2015-02-04 09:34:28 +01:00
Anthony Restaino 9eedc19b11 Merge pull request #176 from kuc/fix-travis-build
Fix Travis build in dev branch
2015-02-03 08:22:50 -05:00
Miłosz Sieradzki 675df18a7d Port changes from setup-ant.sh to setup-ant.bat 2015-02-01 18:55:32 +01:00
Miłosz Sieradzki 382cfdbc65 Change libraries to library projects 2015-02-01 18:04:27 +01:00
Miłosz Sieradzki ddfc5d9334 Finish updating target to android-21 2015-02-01 13:05:40 +01:00
Anthony Restaino 5edbff4f39 Update .travis.yml 2015-01-31 23:36:10 -05:00
Anthony Restaino 86aefa5e54 Update setup-ant.sh 2015-01-31 23:36:05 -05:00
Anthony Restaino 1e647c8e78 Merge pull request #175 from anthonycr/master
Merge in travis build script updates
2015-01-31 23:23:03 -05:00
Anthony Restaino 4f1a1f3aa9 Merge pull request #171 from bidu-dw/dev
Change several search engine URLs to https
2015-01-31 23:11:22 -05:00
Anthony Restaino cbfacffff7 Merge pull request #173 from DF1E/dev
update german string
2015-01-31 23:09:23 -05:00
Anthony Restaino ac48ddfbce Merge pull request #174 from kuc/update-travis-build-script
Update Travis build script
2015-01-31 23:05:11 -05:00
Anthony Restaino 489a814f54 Changes to make Incognito mode more secure and less likely to leak data to websites.
Changes for Incognito Settings
* Always disable location (even if explicitly set in settings)
* Never save passwords or form data
* Always set mixed content mode to NEVER ALLOW
* Disable DOM storage
2015-01-31 22:36:19 -05:00
Anthony Restaino 43950d4f71 Bug Fixes for rouge Android versions/OEMs that don't behave correctly 2015-01-31 11:01:24 -05:00
Miłosz Sieradzki 76178eddc6 Update Travis build script 2015-01-31 13:47:19 +01:00
DF1E cec457fc37 update german string 2015-01-31 10:20:08 +01:00
bidu-dw 76ab43743d Change several search engine URLs to https
Yahoo, Bing, Baidu and Yandex support https. Change for more safety.
2015-01-31 12:59:34 +08:00
Anthony Restaino 9ffeecc584 Improve rendering 2015-01-30 22:50:31 -05:00
Anthony Restaino 0e212539e9 Fixed occasional IllegalStateException 2015-01-30 22:50:15 -05:00
Anthony Restaino 6407f1101a Cache objects to use less memory 2015-01-30 22:49:55 -05:00
Anthony Restaino 03ac2f8b42 Final updates for second Lollipop beta 2015-01-30 21:25:09 -05:00
Anthony Restaino 35c585b3f4 Search adapter shouldnt spawn new worker threads if current ones havent finished 2015-01-29 21:57:39 -05:00
Anthony Restaino 04c4d202b2 Add utility method to help finding favicons 2015-01-29 21:57:19 -05:00
Anthony Restaino 6269325a44 Revert "Revert "Fix miscellaneous lint errors""
This reverts commit bd308dead7.
2015-01-29 21:56:48 -05:00
Anthony Restaino bd308dead7 Revert "Fix miscellaneous lint errors"
This reverts commit 920113b49a.
2015-01-29 21:56:35 -05:00
Anthony Restaino 920113b49a Fix miscellaneous lint errors 2015-01-29 21:56:10 -05:00
Anthony Restaino 7f8253b470 Settings should utilize .apply() instead of .commit() 2015-01-29 21:55:46 -05:00
Anthony Restaino 17afd700d8 Update with new hosts file 2015-01-29 21:54:04 -05:00
Anthony Restaino 7e23135824 Simplified and improved filter algorithm 2015-01-29 20:31:33 -05:00
Anthony Restaino 8314676918 Added option to enable/disable Color Mode 2015-01-29 15:39:53 -05:00
Anthony Restaino c2b436ecfe Allow Sharing in Incognito Mode 2015-01-29 14:30:02 -05:00
Anthony Restaino a897ae4d3e Added option to block third party cookies 2015-01-29 13:52:56 -05:00
Anthony Restaino 9853804fd8 Complete Material Design for Settings 2015-01-29 13:25:19 -05:00
Anthony Restaino 42de0b3ae7 Fixed potential NullPointerExceptions
Rather than try to correct the issue of the Comparator crashing in
BookmarkManager because the Strings/HistoryItems were null, I modified
the HistoryItem object so that the title, url, and folder strings can no
longer be null but will instead be empty if set to null, this then
prevents the BookmarkManager from throwing an NPE when sorting the items
by title.
2015-01-29 09:26:46 -05:00
Anthony Restaino 1eed3ca948 Fixed potential NullPointerExeptions 2015-01-28 21:17:44 -05:00
Anthony Restaino 8be2b62601 Fixed styling issues with the toolbar 2015-01-28 21:17:25 -05:00
Anthony Restaino a201f88906 Merge pull request #168 from DF1E/dev
material design for erveryone..
2015-01-28 19:02:16 -05:00
DF1E a292c6c776 material design for erveryone..
use appcompat on all API's because of its backward compatibility
2015-01-28 19:16:00 +01:00
Anthony Restaino b4714a17c8 Update manifest to latest update 2015-01-27 20:28:03 -05:00
Anthony Restaino eff7e3800d Retrieve correct package name rather than hardcoding it 2015-01-27 20:27:54 -05:00
Anthony Restaino bdb36be4e4 Modified icon 2015-01-27 20:26:24 -05:00
Anthony Restaino 3c9f63b0a6 Simplify main activity layout file 2015-01-27 19:18:09 -05:00
Anthony Restaino 61d569bb7d Merge branch 'dev' of https://github.com/anthonycr/Lightning-Browser into dev 2015-01-27 11:38:59 -05:00
Anthony Restaino 376ac564b8 Animate toolbar hide/show 2015-01-27 11:38:13 -05:00
Anthony Restaino 785449fad6 Update README.md 2015-01-26 13:35:29 -05:00
Anthony Restaino c9e2026526 Merge pull request #167 from anthonycr/master
Add specific build information for each branch
2015-01-26 13:28:02 -05:00
Anthony Restaino ced119f311 Initial changes for Material Design 2015-01-26 13:09:27 -05:00
Anthony Restaino 7b8088f3d4 Activities now return correct menus that can be utilized by the toolbar 2015-01-26 13:07:57 -05:00
Anthony Restaino 762fe5b55b Add text reflow for Android versions >= Kitkat 2015-01-26 13:06:39 -05:00
Anthony Restaino fd781f4a63 JavaScript for inverted rendering 2015-01-26 13:06:02 -05:00
Anthony Restaino dd919513c1 Added material design drop shadow to html pages 2015-01-26 12:58:10 -05:00
336 arquivos alterados com 62361 adições e 32731 exclusões
-22
Ver Arquivo
@@ -1,22 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
+34 -147
Ver Arquivo
@@ -1,165 +1,52 @@
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
gen/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
proguard/
# Android Studio
*.jks
.DS_Store
/local.properties
/.idea/workspace.xml
/build
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Local configuration file (sdk path, etc)
local.properties
gradle.properties
.directory
# Intellij project files
*.ipr
*.iws
.idea/
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
# Gradle
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
.gradle
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
# https://gist.github.com/AltNico/c581f370b3f88715876b
*.apk
*.ap_
*.dex
*.class
build.xml
.DS_Store
gen/
.gradle/
proguard/
out
.settings/
*.swp
*~
# Source:
# https://raw.githubusercontent.com/github/gitignore/master/Android.gitignore
# https://gitlab.com/fdroid/fdroidclient/raw/master/.gitignore
+8 -4
Ver Arquivo
@@ -1,15 +1,19 @@
language: android
sudo: false
android:
components:
- build-tools-19.1.0
- android-19
- build-tools-22.0.1
- build-tools-23.0.0
- android-23
- android-22
- extra-android-support
- extra-android-m2repository
licenses:
- 'android-sdk-license-.+'
- '.*intel.+'
before_install:
- chmod +x gradlew
- git submodule update --init --recursive
install:
- ./setup-ant.sh
- ./gradlew
script:
- ant debug && lint -w --exitcode --disable MissingTranslation .
+19
Ver Arquivo
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="Lightning-Browser" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+10 -2
Ver Arquivo
@@ -6,8 +6,11 @@
* [Download from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones)
####Master Branch: [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=master)](https://travis-ci.org/anthonycr/Lightning-Browser)
####Dev Branch: [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=dev)](https://travis-ci.org/anthonycr/Lightning-Browser)
####Master Branch
* [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=master)](https://travis-ci.org/anthonycr/Lightning-Browser)
####Dev Branch
* [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=dev)](https://travis-ci.org/anthonycr/Lightning-Browser)
####Features
* Bookmarks
@@ -48,6 +51,11 @@
* Please contribute code back if you can. The code isn't perfect.
* Please add translations/translation fixes as you see need
####Contributing
* Contributions are always welcome
* If you want a feature and can code, feel free to fork and add the change yourself and make a pull request
* PLEASE use the ````dev```` branch when contributing as the ````master```` branch is supposed to be for stable builds. I will not reject your pull request if you make it on master, but it will annoy me and make my life harder.
####Setting Up the Project
Due to the inclusion of the netcipher library for Orbot proxy support, importing the project will show you some errors. To fix this, first run the following git command in your project folder (NOTE: You need the git command installed to use this):
````
+3
Ver Arquivo
@@ -0,0 +1,3 @@
/build
*.apk
manifest-merger-release-report.txt
+128
Ver Arquivo
@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Lightning-Browser" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="lightningPlusDebug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleLightningPlusDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileLightningPlusDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleLightningPlusDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileLightningPlusDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateLightningPlusDebugAndroidTestSources</task>
<task>generateLightningPlusDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/lightningPlus/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/lightningPlus/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/lightningPlus/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/lightningPlus/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/lightningPlus/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/lightningPlus/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/lightningPlus/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/lightningPlus/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/lightningPlus/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/lightningPlus/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/lightningPlus/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/lightningPlus/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/lightningPlus/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/lightningPlus/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/palette-v7/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/net.i2p.android/client/0.7/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="client-0.7" level="project" />
<orderEntry type="library" exported="" name="design-23.0.0" level="project" />
<orderEntry type="library" exported="" name="palette-v7-23.0.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.0" level="project" />
<orderEntry type="library" exported="" name="jsoup-1.8.1" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.0.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" />
<orderEntry type="module" module-name="libnetcipher" exported="" />
</component>
</module>
+56
Ver Arquivo
@@ -0,0 +1,56 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.0"
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
versionName "4.1.1a"
}
sourceSets {
lightningPlus.setRoot('src/LightningPlus')
lightningLite.setRoot('src/LightningLite')
}
buildTypes {
debug {
minifyEnabled false
shrinkResources false
proguardFiles 'proguard-project.txt'
}
release {
minifyEnabled true
shrinkResources true
proguardFiles 'proguard-project.txt'
}
}
productFlavors {
lightningPlus {
buildConfigField "boolean", "FULL_VERSION", "true"
applicationId "acr.browser.lightning"
versionCode 80
}
lightningLite {
buildConfigField "boolean", "FULL_VERSION", "false"
applicationId "acr.browser.barebones"
versionCode 81
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile 'com.android.support:palette-v7:23.0.0'
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.android.support:design:23.0.0'
compile 'com.android.support:recyclerview-v7:23.0.0'
compile 'org.jsoup:jsoup:1.8.1'
// Only Lightning Plus needs the proxy libraries
lightningPlusCompile 'net.i2p.android:client:0.7'
lightningPlusCompile(project(':libnetcipher'))
// git submodule foreach git reset --hard
// git submodule update --remote
}
@@ -34,12 +34,26 @@
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keep public class acr.browser.lightning.reading.*
-keep class org.lucasr.twowayview.** { *; }
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** w(...);
public static *** i(...);
}
# this will fix a force close in ReadingActivity
-keep public class org.jsoup.** {
public *;
}
# Without this rule, openFileChooser does not get called on KitKat
-keep class acr.browser.lightning.LightningView$LightningChromeClient {
void openFileChooser(android.webkit.ValueCallback);
void openFileChooser(android.webkit.ValueCallback, java.lang.String);
void openFileChooser(android.webkit.ValueCallback, java.lang.String, java.lang.String);
-keep class acr.browser.lightning.view.LightningView$LightningChromeClient {
void openFileChooser(android.webkit.ValueCallback);
void openFileChooser(android.webkit.ValueCallback, java.lang.String);
void openFileChooser(android.webkit.ValueCallback, java.lang.String, java.lang.String);
}
-keepclasseswithmembernames class * {
@@ -66,3 +80,16 @@
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
# The I2P Java API bundled inside the I2P Android client library contains
# references to javax.naming classes that Android doesn't have. But those
# classes are never used on Android, and it is safe to ignore the warnings.
-dontwarn net.i2p.crypto.CertUtil
-dontwarn org.apache.http.conn.ssl.DefaultHostnameVerifier
-dontwarn org.apache.http.HttpHost
@@ -0,0 +1,58 @@
package acr.browser.lightning.utils;
import android.app.Activity;
import android.content.Context;
/**
* 6/4/2015 Anthony Restaino
*/
public class ProxyUtils {
private static ProxyUtils mInstance;
private ProxyUtils(Context context) {
}
public static ProxyUtils getInstance(Context context) {
if (mInstance == null) {
mInstance = new ProxyUtils(context);
}
return mInstance;
}
/*
* If Orbot/Tor or I2P is installed, prompt the user if they want to enable
* proxying for this session
*/
public void checkForProxy(final Activity activity) {
}
/*
* Initialize WebKit Proxying
*/
private void initializeProxy(Activity activity) {
}
public boolean isProxyReady(Context context) {
return true;
}
public void updateProxySettings(Activity activity) {
}
public void onStop() {
}
public void onStart(final Activity activity) {
}
public int setProxyChoice(int choice, Activity activity) {
return choice;
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,215 @@
package acr.browser.lightning.utils;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import net.i2p.android.ui.I2PAndroidHelper;
import acr.browser.lightning.R;
import acr.browser.lightning.activity.BrowserApp;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import info.guardianproject.netcipher.web.WebkitProxy;
/**
* 6/4/2015 Anthony Restaino
*/
public class ProxyUtils {
// Helper
private I2PAndroidHelper mI2PHelper;
private static boolean mI2PHelperBound;
private static boolean mI2PProxyInitialized;
private PreferenceManager mPreferences;
private static ProxyUtils mInstance;
private ProxyUtils(Context context) {
mPreferences = PreferenceManager.getInstance();
mI2PHelper = new I2PAndroidHelper(context);
}
public static ProxyUtils getInstance(@NonNull Context context) {
if (mInstance == null) {
mInstance = new ProxyUtils(context);
}
return mInstance;
}
/*
* If Orbot/Tor or I2P is installed, prompt the user if they want to enable
* proxying for this session
*/
public void checkForProxy(final Activity activity) {
boolean useProxy = mPreferences.getUseProxy();
final boolean orbotInstalled = OrbotHelper.isOrbotInstalled(activity);
boolean orbotChecked = mPreferences.getCheckedForTor();
boolean orbot = orbotInstalled && !orbotChecked;
boolean i2pInstalled = mI2PHelper.isI2PAndroidInstalled();
boolean i2pChecked = mPreferences.getCheckedForI2P();
boolean i2p = i2pInstalled && !i2pChecked;
// TODO Is the idea to show this per-session, or only once?
if (!useProxy && (orbot || i2p)) {
if (orbot) mPreferences.setCheckedForTor(true);
if (i2p) mPreferences.setCheckedForI2P(true);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (orbotInstalled && i2pInstalled) {
String[] proxyChoices = activity.getResources().getStringArray(R.array.proxy_choices_array);
builder.setTitle(activity.getResources().getString(R.string.http_proxy))
.setSingleChoiceItems(proxyChoices, mPreferences.getProxyChoice(),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setProxyChoice(which);
}
})
.setNeutralButton(activity.getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mPreferences.getUseProxy())
initializeProxy(activity);
}
});
} else {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
mPreferences.setProxyChoice(orbotInstalled ?
Constants.PROXY_ORBOT : Constants.PROXY_I2P);
initializeProxy(activity);
break;
case DialogInterface.BUTTON_NEGATIVE:
mPreferences.setProxyChoice(Constants.NO_PROXY);
break;
}
}
};
builder.setMessage(orbotInstalled ? R.string.use_tor_prompt : R.string.use_i2p_prompt)
.setPositiveButton(R.string.yes, dialogClickListener)
.setNegativeButton(R.string.no, dialogClickListener);
}
builder.show();
}
}
/*
* Initialize WebKit Proxying
*/
private void initializeProxy(Activity activity) {
String host;
int port;
switch (mPreferences.getProxyChoice()) {
case Constants.NO_PROXY:
// We shouldn't be here
return;
case Constants.PROXY_ORBOT:
if (!OrbotHelper.isOrbotRunning(activity))
OrbotHelper.requestStartTor(activity);
host = "localhost";
port = 8118;
break;
case Constants.PROXY_I2P:
mI2PProxyInitialized = true;
if (mI2PHelperBound && !mI2PHelper.isI2PAndroidRunning()) {
mI2PHelper.requestI2PAndroidStart(activity);
}
host = "localhost";
port = 4444;
break;
default:
host = mPreferences.getProxyHost();
port = mPreferences.getProxyPort();
}
try {
WebkitProxy.setProxy(BrowserApp.class.getName(), activity.getApplicationContext(), null, host, port);
} catch (Exception e) {
Log.d(Constants.TAG, "error enabling web proxying", e);
}
}
public boolean isProxyReady(Activity activity) {
if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) {
if (!mI2PHelper.isI2PAndroidRunning()) {
Utils.showSnackbar(activity, R.string.i2p_not_running);
return false;
} else if (!mI2PHelper.areTunnelsActive()) {
Utils.showSnackbar(activity, R.string.i2p_tunnels_not_ready);
return false;
}
}
return true;
}
public void updateProxySettings(Activity activity) {
if (mPreferences.getUseProxy()) {
initializeProxy(activity);
} else {
try {
WebkitProxy.resetProxy(BrowserApp.class.getName(), activity.getApplicationContext());
} catch (Exception e) {
e.printStackTrace();
}
mI2PProxyInitialized = false;
}
}
public void onStop() {
mI2PHelper.unbind();
mI2PHelperBound = false;
}
public void onStart(final Activity activity) {
if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) {
// Try to bind to I2P Android
mI2PHelper.bind(new I2PAndroidHelper.Callback() {
@Override
public void onI2PAndroidBound() {
mI2PHelperBound = true;
if (mI2PProxyInitialized && !mI2PHelper.isI2PAndroidRunning())
mI2PHelper.requestI2PAndroidStart(activity);
}
});
}
}
public int setProxyChoice(int choice, Activity activity) {
switch (choice) {
case Constants.PROXY_ORBOT:
if (!OrbotHelper.isOrbotInstalled(activity)) {
choice = Constants.NO_PROXY;
Utils.showSnackbar(activity, R.string.install_orbot);
}
break;
case Constants.PROXY_I2P:
I2PAndroidHelper ih = new I2PAndroidHelper(activity.getApplicationContext());
if (!ih.isI2PAndroidInstalled()) {
choice = Constants.NO_PROXY;
ih.promptToInstall(activity);
}
break;
case Constants.PROXY_MANUAL:
break;
}
return choice;
}
}
@@ -1,20 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014 A.C.R. Development
-->
<!-- Copyright 2014 A.C.R. Development -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="acr.browser.lightning"
android:versionCode="66"
android:versionName="3.2.0.1a" >
package="acr.browser.lightning" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
<uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature
android:name="android.hardware.location.gps"
@@ -26,23 +19,19 @@
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="20" />
<application
android:name="acr.browser.lightning.BrowserApp"
android:name=".activity.BrowserApp"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="acr.browser.lightning.MainActivity"
android:name=".activity.MainActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/LightTheme" >
android:theme="@style/Theme.LightTheme" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -104,10 +93,10 @@
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.SettingsActivity"
android:name=".activity.SettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings"
android:theme="@style/DefaultTheme" >
android:theme="@style/Theme.SettingsTheme" >
<intent-filter>
<action android:name="android.intent.action.SETTINGS" />
@@ -115,67 +104,12 @@
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.GeneralSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings_general"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.GENERAL_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.DisplaySettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings_display"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.DISPLAY_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.PrivacySettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings_privacy"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.PRIVACY_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.AdvancedSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings_advanced"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.ADVANCED_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.AboutSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/settings_about"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.ABOUT_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.IncognitoActivity"
android:name=".activity.IncognitoActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/DarkTheme"
android:theme="@style/Theme.DarkTheme"
android:windowSoftInputMode="stateHidden" >
<intent-filter>
<action android:name="android.intent.action.INCOGNITO" />
@@ -184,24 +118,12 @@
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.LicenseActivity"
android:name=".activity.ReadingActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/licenses"
android:theme="@style/DefaultTheme" >
android:label="@string/reading_mode"
android:theme="@style/Theme.SettingsTheme" >
<intent-filter>
<action android:name="android.intent.action.LICENSE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="acr.browser.lightning.BookmarkActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/bookmark_settings"
android:theme="@style/DefaultTheme" >
<intent-filter>
<action android:name="android.intent.action.BOOKMARK" />
<action android:name="android.intent.action.READING" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 89 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 3.4 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 3.4 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 12 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 12 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 20 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 20 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 14 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 14 KiB

Ver Arquivo

Antes

Largura:  |  Altura:  |  Tamanho: 21 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 21 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 14 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 14 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 44 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 44 KiB

Antes

Largura:  |  Altura:  |  Tamanho: 18 KiB

Depois

Largura:  |  Altura:  |  Tamanho: 18 KiB

@@ -0,0 +1,125 @@
package acr.browser.lightning.activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import acr.browser.lightning.R;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
* <p/>
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@NonNull
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onPause() {
super.onPause();
if (isFinishing()) {
overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_out_to_right);
}
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,19 @@
package acr.browser.lightning.activity;
import android.app.Application;
import android.content.Context;
public class BrowserApp extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
public static Context getAppContext() {
return context;
}
}
@@ -0,0 +1,62 @@
package acr.browser.lightning.activity;
import android.content.Intent;
import android.os.Build;
import android.view.Menu;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
@SuppressWarnings("deprecation")
public class IncognitoActivity extends BrowserActivity {
@Override
public void updateCookiePreference() {
CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(this);
}
cookieManager.setAcceptCookie(PreferenceManager.getInstance().getIncognitoCookiesEnabled());
}
@Override
public synchronized void initializeTabs() {
newTab(null, true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.incognito, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onNewIntent(Intent intent) {
// handleNewIntent(intent);
super.onNewIntent(intent);
}
@Override
protected void onPause() {
super.onPause();
// saveOpenTabs();
}
@Override
public void updateHistory(String title, String url) {
// addItemToHistory(title, url);
}
@Override
public boolean isIncognito() {
return true;
}
@Override
public void closeActivity() {
closeDrawers();
finish();
}
}
@@ -0,0 +1,63 @@
package acr.browser.lightning.activity;
import android.content.Intent;
import android.os.Build;
import android.view.Menu;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
@SuppressWarnings("deprecation")
public class MainActivity extends BrowserActivity {
@Override
public void updateCookiePreference() {
CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(this);
}
cookieManager.setAcceptCookie(PreferenceManager.getInstance().getCookiesEnabled());
}
@Override
public synchronized void initializeTabs() {
restoreOrNewTab();
// if incognito mode use newTab(null, true); instead
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onNewIntent(Intent intent) {
handleNewIntent(intent);
super.onNewIntent(intent);
}
@Override
protected void onPause() {
super.onPause();
saveOpenTabs();
}
@Override
public void updateHistory(String title, String url) {
addItemToHistory(title, url);
}
@Override
public boolean isIncognito() {
return false;
}
@Override
public void closeActivity() {
closeDrawers();
moveTaskToBack(true);
}
}
@@ -0,0 +1,284 @@
package acr.browser.lightning.activity;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.reading.HtmlFetcher;
import acr.browser.lightning.reading.JResult;
import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.Utils;
public class ReadingActivity extends AppCompatActivity {
private TextView mTitle;
private TextView mBody;
private boolean mInvert;
private String mUrl = null;
private PreferenceManager mPreferences;
private int mTextSize;
private ProgressDialog mProgressDialog;
private static final float XXLARGE = 30.0f;
private static final float XLARGE = 26.0f;
private static final float LARGE = 22.0f;
private static final float MEDIUM = 18.0f;
private static final float SMALL = 14.0f;
private static final float XSMALL = 10.0f;
@Override
protected void onCreate(Bundle savedInstanceState) {
mPreferences = PreferenceManager.getInstance();
mInvert = mPreferences.getInvertColors();
final int color;
if (mInvert) {
setTheme(R.style.Theme_SettingsTheme_Dark);
color = ThemeUtils.getPrimaryColorDark(this);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
} else {
setTheme(R.style.Theme_SettingsTheme);
color = ThemeUtils.getPrimaryColor(this);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
}
super.onCreate(savedInstanceState);
setContentView(R.layout.reading_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mTitle = (TextView) findViewById(R.id.textViewTitle);
mBody = (TextView) findViewById(R.id.textViewBody);
mTextSize = mPreferences.getReadingTextSize();
mBody.setTextSize(getTextSize(mTextSize));
mTitle.setText(getString(R.string.untitled));
mBody.setText(getString(R.string.loading));
mTitle.setVisibility(View.INVISIBLE);
mBody.setVisibility(View.INVISIBLE);
Intent intent = getIntent();
if (!loadPage(intent)) {
setText(getString(R.string.untitled), getString(R.string.loading_failed));
}
}
private static float getTextSize(int size) {
switch (size) {
case 0:
return XSMALL;
case 1:
return SMALL;
case 2:
return MEDIUM;
case 3:
return LARGE;
case 4:
return XLARGE;
case 5:
return XXLARGE;
default:
return MEDIUM;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.reading, menu);
MenuItem invert = menu.findItem(R.id.invert_item);
MenuItem textSize = menu.findItem(R.id.text_size_item);
int iconColor = mInvert ? ThemeUtils.getIconDarkThemeColor(this) : ThemeUtils.getIconLightThemeColor(this);
if (invert != null && invert.getIcon() != null)
invert.getIcon().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
if (textSize != null && textSize.getIcon() != null)
textSize.getIcon().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
return super.onCreateOptionsMenu(menu);
}
private boolean loadPage(Intent intent) {
if (intent == null) {
return false;
}
mUrl = intent.getStringExtra(Constants.LOAD_READING_URL);
if (mUrl == null) {
return false;
}
if (getSupportActionBar() != null)
getSupportActionBar().setTitle(Utils.getDomainName(mUrl));
new PageLoader(this).execute(mUrl);
return true;
}
private class PageLoader extends AsyncTask<String, Void, Void> {
private final Activity mActivity;
private String mTitleText;
private String mBodyText;
public PageLoader(Activity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog = new ProgressDialog(mActivity);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCancelable(false);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setMessage(mActivity.getString(R.string.loading));
mProgressDialog.show();
}
@Override
protected Void doInBackground(String... params) {
HtmlFetcher fetcher = new HtmlFetcher();
try {
JResult result = fetcher.fetchAndExtract(params[0], 2500, true);
mTitleText = result.getTitle();
mBodyText = result.getText();
} catch (Exception e) {
mTitleText = "";
mBodyText = "";
e.printStackTrace();
} catch (OutOfMemoryError e) {
System.gc();
mTitleText = "";
mBodyText = "";
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
if (mTitleText.isEmpty() || mBodyText.isEmpty()) {
setText(getString(R.string.untitled), getString(R.string.loading_failed));
} else {
setText(mTitleText, mBodyText);
}
super.onPostExecute(result);
}
}
private void setText(String title, String body) {
if (mTitle == null || mBody == null)
return;
if (mTitle.getVisibility() == View.INVISIBLE) {
mTitle.setAlpha(0.0f);
mTitle.setVisibility(View.VISIBLE);
mTitle.setText(title);
ObjectAnimator animator = ObjectAnimator.ofFloat(mTitle, "alpha", 1.0f);
animator.setDuration(300);
animator.start();
} else {
mTitle.setText(title);
}
if (mBody.getVisibility() == View.INVISIBLE) {
mBody.setAlpha(0.0f);
mBody.setVisibility(View.VISIBLE);
mBody.setText(body);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBody, "alpha", 1.0f);
animator.setDuration(300);
animator.start();
} else {
mBody.setText(body);
}
}
@Override
protected void onDestroy() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.invert_item:
mPreferences.setInvertColors(!mInvert);
Intent read = new Intent(this, ReadingActivity.class);
read.putExtra(Constants.LOAD_READING_URL, mUrl);
startActivity(read);
finish();
break;
case R.id.text_size_item:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
View view = inflater.inflate(R.layout.seek_layout, null);
final SeekBar bar = (SeekBar) view.findViewById(R.id.text_size_seekbar);
bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar view, int size, boolean user) {
mBody.setTextSize(getTextSize(size));
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {
}
@Override
public void onStopTrackingTouch(SeekBar arg0) {
}
});
bar.setMax(5);
bar.setProgress(mTextSize);
builder.setView(view);
builder.setTitle(R.string.size);
builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
mTextSize = bar.getProgress();
mBody.setTextSize(getTextSize(mTextSize));
mPreferences.setReadingTextSize(bar.getProgress());
}
});
builder.show();
break;
default:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;
import acr.browser.lightning.R;
import acr.browser.lightning.utils.PermissionsManager;
public class SettingsActivity extends ThemableSettingsActivity {
private static final List<String> fragments = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// this is a workaround for the Toolbar in PreferenceActitivty
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
LinearLayout content = (LinearLayout) root.getChildAt(0);
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.toolbar_settings, null);
root.removeAllViews();
toolbarContainer.addView(content);
root.addView(toolbarContainer);
// now we can set the Toolbar using AppCompatPreferenceActivity
Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preferences_headers, target);
fragments.clear();
for (Header header : target) {
fragments.add(header.fragment);
}
}
@Override
protected boolean isValidFragment(String fragmentName) {
return fragments.contains(fragmentName);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
finish();
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@@ -0,0 +1,49 @@
package acr.browser.lightning.activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
public abstract class ThemableBrowserActivity extends AppCompatActivity {
private int mTheme;
private boolean mShowTabsInDrawer;
@Override
protected void onCreate(Bundle savedInstanceState) {
mTheme = PreferenceManager.getInstance().getUseTheme();
mShowTabsInDrawer = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet());
// set the theme
if (mTheme == 1) {
setTheme(R.style.Theme_DarkTheme);
} else if (mTheme == 2) {
setTheme(R.style.Theme_BlackTheme);
}
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
int theme = PreferenceManager.getInstance().getUseTheme();
boolean drawerTabs = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet());
if (theme != mTheme || mShowTabsInDrawer != drawerTabs) {
restart();
}
}
public boolean isTablet() {
return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
private void restart() {
Intent intent = getIntent();
finish();
startActivity(intent);
}
}
@@ -0,0 +1,43 @@
package acr.browser.lightning.activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.ThemeUtils;
public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivity {
private int mTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
mTheme = PreferenceManager.getInstance().getUseTheme();
// set the theme
if (mTheme == 0) {
setTheme(R.style.Theme_SettingsTheme);
this.getWindow().setBackgroundDrawable(new ColorDrawable(ThemeUtils.getPrimaryColor(this)));
} else if (mTheme == 1) {
setTheme(R.style.Theme_SettingsTheme_Dark);
this.getWindow().setBackgroundDrawable(new ColorDrawable(ThemeUtils.getPrimaryColorDark(this)));
} else if (mTheme == 2) {
setTheme(R.style.Theme_SettingsTheme_Black);
this.getWindow().setBackgroundDrawable(new ColorDrawable(ThemeUtils.getPrimaryColorDark(this)));
}
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (PreferenceManager.getInstance().getUseTheme() != mTheme) {
restart();
}
}
private void restart() {
recreate();
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.constant;
import android.app.Activity;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import acr.browser.lightning.R;
import acr.browser.lightning.activity.BrowserApp;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils;
public class BookmarkPage {
public static final String FILENAME = "bookmarks.html";
public static final String HEADING = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" +
"<head>\n" +
"<meta content=en-us http-equiv=Content-Language />\n" +
"<meta content='text/html; charset=utf-8' http-equiv=Content-Type />\n" +
"<meta name=viewport content='width=device-width, initial-scale=1.0'>\n" +
"<title>" +
BrowserApp.getAppContext().getString(R.string.action_bookmarks) +
"</title>\n" +
"</head>\n" +
"<style>body{background:#e1e1e1;max-width:100%;min-height:100%}#content{width:100%;max-width:800px;margin:0 auto;text-align:center}.box{vertical-align:middle;text-align:center;position:relative;display:inline-block;height:45px;width:150px;margin:10px;background-color:#fff;box-shadow:0 3px 6px rgba(0,0,0,0.25);font-family:Arial;color:#444;font-size:12px;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}.box-content{height:25px;width:100%;vertical-align:middle;text-align:center;display:table-cell}p.ellipses{" +
"width:130px;font-size: small;font-family: Arial, Helvetica, 'sans-serif';white-space:nowrap;overflow:hidden;text-align:left;vertical-align:middle;margin:auto;text-overflow:ellipsis;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis}.box a{width:100%;height:100%;position:absolute;left:0;top:0}img{vertical-align:middle;margin-right:10px;width:20px;height:20px;}.margin{margin:10px}</style>\n" +
"<body><div id=content>";
public static final String PART1 = "<div class=box><a href='";
public static final String PART2 = "'></a>\n" +
"<div class=margin>\n" +
"<div class=box-content>\n" +
"<p class=ellipses>\n" +
"<img src='";
public static final String PART3 = "http://www.google.com/s2/favicons?domain=";
public static final String PART4 = "' />";
public static final String PART5 = "</p></div></div></div>";
public static final String END = "</div></body></html>";
public static void buildBookmarkPage(final Activity activity, final String folder, final List<HistoryItem> list) {
final BookmarkManager manager = BookmarkManager.getInstance(activity);
File bookmarkWebPage;
if (folder == null || folder.isEmpty()) {
bookmarkWebPage = new File(activity.getFilesDir(), BookmarkPage.FILENAME);
} else {
bookmarkWebPage = new File(activity.getFilesDir(), folder + '-' + BookmarkPage.FILENAME);
}
final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING);
String folderIconPath = Constants.FILE + activity.getCacheDir() + "/folder.png";
for (int n = 0; n < list.size(); n++) {
final HistoryItem item = list.get(n);
bookmarkBuilder.append(BookmarkPage.PART1);
if (item.isFolder()) {
File folderPage = new File(activity.getFilesDir(), item.getTitle() + '-' + BookmarkPage.FILENAME);
bookmarkBuilder.append(Constants.FILE).append(folderPage);
bookmarkBuilder.append(BookmarkPage.PART2);
bookmarkBuilder.append(folderIconPath);
new Thread(new Runnable() {
@Override
public void run() {
buildBookmarkPage(activity, item.getTitle(), manager.getBookmarksFromFolder(item.getTitle(), true));
}
}).run();
} else {
bookmarkBuilder.append(item.getUrl());
bookmarkBuilder.append(BookmarkPage.PART2).append(BookmarkPage.PART3);
bookmarkBuilder.append(item.getUrl());
}
bookmarkBuilder.append(BookmarkPage.PART4);
bookmarkBuilder.append(item.getTitle());
bookmarkBuilder.append(BookmarkPage.PART5);
}
bookmarkBuilder.append(BookmarkPage.END);
FileWriter bookWriter = null;
try {
bookWriter = new FileWriter(bookmarkWebPage, false);
bookWriter.write(bookmarkBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.close(bookWriter);
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.constant;
import android.os.Environment;
import acr.browser.lightning.BuildConfig;
public final class Constants {
private Constants() {
}
public static final boolean FULL_VERSION = BuildConfig.FULL_VERSION;
public static final String DESKTOP_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36";
public static final String MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; U; Android 4.4; en-us; Nexus 4 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
public static final String YAHOO_SEARCH = "https://search.yahoo.com/search?p=";
public static final String GOOGLE_SEARCH = "https://www.google.com/search?client=lightning&ie=UTF-8&oe=UTF-8&q=";
public static final String BING_SEARCH = "https://www.bing.com/search?q=";
public static final String DUCK_SEARCH = "https://duckduckgo.com/?t=lightning&q=";
public static final String DUCK_LITE_SEARCH = "https://duckduckgo.com/lite/?t=lightning&q=";
public static final String STARTPAGE_MOBILE_SEARCH = "https://startpage.com/do/m/mobilesearch?language=english&query=";
public static final String STARTPAGE_SEARCH = "https://startpage.com/do/search?language=english&query=";
public static final String ASK_SEARCH = "http://www.ask.com/web?qsrc=0&o=0&l=dir&qo=lightningBrowser&q=";
public static final String HOMEPAGE = "about:home";
public static final String BAIDU_SEARCH = "https://www.baidu.com/s?wd=";
public static final String YANDEX_SEARCH = "https://yandex.ru/yandsearch?lr=21411&text=";
public static final String EXTERNAL_STORAGE = Environment.getExternalStorageDirectory()
.toString();
public static final String JAVASCRIPT_INVERT_PAGE = "javascript:(function(){var e='img {-webkit-filter: invert(100%);'+'-moz-filter: invert(100%);'+'-o-filter: invert(100%);'+'-ms-filter: invert(100%); }',t=document.getElementsByTagName('head')[0],n=document.createElement('style');if(!window.counter){window.counter=1}else{window.counter++;if(window.counter%2==0){var e='html {-webkit-filter: invert(0%); -moz-filter: invert(0%); -o-filter: invert(0%); -ms-filter: invert(0%); }'}}n.type='text/css';if(n.styleSheet){n.styleSheet.cssText=e}else{n.appendChild(document.createTextNode(e))}t.appendChild(n)})();";
public static final String JAVASCRIPT_TEXT_REFLOW = "javascript:document.getElementsByTagName('body')[0].style.width=window.innerWidth+'px';";
public static final String LOAD_READING_URL = "ReadingUrl";
public static final String SEPARATOR = "\\|\\$\\|SEPARATOR\\|\\$\\|";
public static final String HTTP = "http://";
public static final String HTTPS = "https://";
public static final String FILE = "file://";
public static final String FOLDER = "folder://";
public static final String TAG = "Lightning";
// These should match the order of @array/proxy_choices_array
public static final int NO_PROXY = 0;
public static final int PROXY_ORBOT = 1;
public static final int PROXY_I2P = 2;
public static final int PROXY_MANUAL = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
public static final String[] TEXT_ENCODINGS = {"ISO-8859-1", "UTF-8", "GBK", "Big5", "ISO-2022-JP", "SHIFT_JS", "EUC-JP", "EUC-KR"};
}
@@ -0,0 +1,73 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.constant;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import android.content.Context;
import acr.browser.lightning.activity.BrowserApp;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryDatabase;
import acr.browser.lightning.utils.Utils;
public class HistoryPage {
public static final String FILENAME = "history.html";
private static final String HEADING = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\"><head><meta content=\"en-us\" http-equiv=\"Content-Language\" /><meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\"><title>"
+ BrowserApp.getAppContext().getString(R.string.action_history)
+ "</title></head><style>body { background: #e1e1e1;}.box { vertical-align:middle;position:relative; display: block; margin: 10px;padding-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px; background-color:#fff;box-shadow: 0px 2px 3px rgba( 0, 0, 0, 0.25 );font-family: Arial;color: #444;font-size: 12px;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;}.box a { width: 100%; height: 100%; position: absolute; left: 0; top: 0;}.black {color: black;font-size: 15px;font-family: Arial; white-space: nowrap; overflow: hidden;margin:auto; text-overflow: ellipsis; -o-text-overflow: ellipsis; -ms-text-overflow: ellipsis;}.font {color: gray;font-size: 10px;font-family: Arial; white-space: nowrap; overflow: hidden;margin:auto; text-overflow: ellipsis; -o-text-overflow: ellipsis; -ms-text-overflow: ellipsis;}</style><body><div id=\"content\">";
private static final String PART1 = "<div class=\"box\"><a href=\"";
private static final String PART2 = "\"></a><p class=\"black\">";
private static final String PART3 = "</p><p class=\"font\">";
private static final String PART4 = "</p></div></div>";
private static final String END = "</div></body></html>";
public static String getHistoryPage(Context context) {
StringBuilder historyBuilder = new StringBuilder(HistoryPage.HEADING);
List<HistoryItem> historyList = getWebHistory(context);
Iterator<HistoryItem> it = historyList.iterator();
HistoryItem helper;
while (it.hasNext()) {
helper = it.next();
historyBuilder.append(HistoryPage.PART1);
historyBuilder.append(helper.getUrl());
historyBuilder.append(HistoryPage.PART2);
historyBuilder.append(helper.getTitle());
historyBuilder.append(HistoryPage.PART3);
historyBuilder.append(helper.getUrl());
historyBuilder.append(HistoryPage.PART4);
}
historyBuilder.append(HistoryPage.END);
File historyWebPage = new File(context.getFilesDir(), FILENAME);
FileWriter historyWriter = null;
try {
historyWriter = new FileWriter(historyWebPage, false);
historyWriter.write(historyBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.close(historyWriter);
}
return Constants.FILE + historyWebPage;
}
private static List<HistoryItem> getWebHistory(Context context) {
HistoryDatabase databaseHandler = HistoryDatabase.getInstance(context
.getApplicationContext());
return databaseHandler.getLastHundredItems();
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.constant;
import android.app.Activity;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import acr.browser.lightning.activity.BrowserApp;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils;
public class StartPage {
public static final String FILENAME = "homepage.html";
public static final String HEAD = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\">"
+ "<head>"
+ "<meta content=\"en-us\" http-equiv=\"Content-Language\" />"
+ "<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" />"
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">"
+ "<title>"
+ BrowserApp.getAppContext().getString(R.string.home)
+ "</title>"
+ "</head>"
+ "<style>body{background:#f2f2f2;text-align:center;margin:0px;}#search_input{height:35px; "
+ "width:100%;outline:none;border:none;font-size: 16px;background-color:transparent;}"
+ "span { display: block; overflow: hidden; padding-left:5px;vertical-align:middle;}"
+ ".search_bar{display:table;vertical-align:middle;width:90%;height:35px;max-width:500px;margin:0 auto;background-color:#fff;box-shadow: 0px 2px 3px rgba( 0, 0, 0, 0.25 );"
+ "font-family: Arial;color: #444;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;}"
+ "#search_submit{outline:none;height:37px;float:right;color:#404040;font-size:16px;font-weight:bold;border:none;"
+ "background-color:transparent;}.outer { display: table; position: absolute; height: 100%; width: 100%;}"
+ ".middle { display: table-cell; vertical-align: middle;}.inner { margin-left: auto; margin-right: auto; "
+ "margin-bottom:10%; width: 100%;}img.smaller{width:50%;max-width:300px;}"
+ ".box { vertical-align:middle;position:relative; display: block; margin: 10px;padding-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;"
+ " background-color:#fff;box-shadow: 0px 3px rgba( 0, 0, 0, 0.1 );font-family: Arial;color: #444;"
+ "font-size: 12px;-moz-border-radius: 2px;-webkit-border-radius: 2px;"
+ "border-radius: 2px;}</style><body> <div class=\"outer\"><div class=\"middle\"><div class=\"inner\"><img class=\"smaller\" src=\"";
public static final String MIDDLE = "\" ></br></br><form onsubmit=\"return search()\" class=\"search_bar\">"
+ "<input type=\"submit\" id=\"search_submit\" value=\"Search\" ><span><input class=\"search\" type=\"text\" value=\"\" id=\"search_input\" >"
+ "</span></form></br></br></div></div></div><script type=\"text/javascript\">function search(){if(document.getElementById(\"search_input\").value != \"\"){window.location.href = \"";
public static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}</script></body></html>";
/**
* This method builds the homepage and returns the local URL to be loaded
* when it finishes building.
*
* @return the URL to load
*/
public static String getHomepage(Activity activity) {
StringBuilder homepageBuilder = new StringBuilder(StartPage.HEAD);
String icon;
String searchUrl;
switch (PreferenceManager.getInstance().getSearchChoice()) {
case 0:
// CUSTOM SEARCH
icon = "file:///android_asset/lightning.png";
searchUrl = PreferenceManager.getInstance().getSearchUrl();
break;
case 1:
// GOOGLE_SEARCH;
icon = "file:///android_asset/google.png";
// "https://www.google.com/images/srpr/logo11w.png";
searchUrl = Constants.GOOGLE_SEARCH;
break;
case 2:
// ANDROID SEARCH;
icon = "file:///android_asset/ask.png";
searchUrl = Constants.ASK_SEARCH;
break;
case 3:
// BING_SEARCH;
icon = "file:///android_asset/bing.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Bing_logo_%282013%29.svg/500px-Bing_logo_%282013%29.svg.png";
searchUrl = Constants.BING_SEARCH;
break;
case 4:
// YAHOO_SEARCH;
icon = "file:///android_asset/yahoo.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Yahoo%21_logo.svg/799px-Yahoo%21_logo.svg.png";
searchUrl = Constants.YAHOO_SEARCH;
break;
case 5:
// STARTPAGE_SEARCH;
icon = "file:///android_asset/startpage.png";
// "https://startpage.com/graphics/startp_logo.gif";
searchUrl = Constants.STARTPAGE_SEARCH;
break;
case 6:
// STARTPAGE_MOBILE
icon = "file:///android_asset/startpage.png";
// "https://startpage.com/graphics/startp_logo.gif";
searchUrl = Constants.STARTPAGE_MOBILE_SEARCH;
break;
case 7:
// DUCK_SEARCH;
icon = "file:///android_asset/duckduckgo.png";
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png";
searchUrl = Constants.DUCK_SEARCH;
break;
case 8:
// DUCK_LITE_SEARCH;
icon = "file:///android_asset/duckduckgo.png";
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png";
searchUrl = Constants.DUCK_LITE_SEARCH;
break;
case 9:
// BAIDU_SEARCH;
icon = "file:///android_asset/baidu.png";
// "http://www.baidu.com/img/bdlogo.gif";
searchUrl = Constants.BAIDU_SEARCH;
break;
case 10:
// YANDEX_SEARCH;
icon = "file:///android_asset/yandex.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Yandex.svg/600px-Yandex.svg.png";
searchUrl = Constants.YANDEX_SEARCH;
break;
default:
// DEFAULT GOOGLE_SEARCH;
icon = "file:///android_asset/google.png";
searchUrl = Constants.GOOGLE_SEARCH;
break;
}
homepageBuilder.append(icon);
homepageBuilder.append(StartPage.MIDDLE);
homepageBuilder.append(searchUrl);
homepageBuilder.append(StartPage.END);
File homepage = new File(activity.getFilesDir(), StartPage.FILENAME);
FileWriter hWriter = null;
try {
hWriter = new FileWriter(homepage, false);
hWriter.write(homepageBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.close(hWriter);
}
return Constants.FILE + homepage;
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.controller;
import android.app.Activity;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Message;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebView;
import acr.browser.lightning.view.LightningView;
public interface BrowserController {
void updateUrl(String title, boolean shortUrl);
void updateProgress(int n);
void updateHistory(String title, String url);
void openFileChooser(ValueCallback<Uri> uploadMsg);
void updateTabs();
void onLongPress();
void onShowCustomView(View view, CustomViewCallback callback);
void onHideCustomView();
Bitmap getDefaultVideoPoster();
View getVideoLoadingProgressView();
void onCreateWindow(Message resultMsg);
void onCloseWindow(LightningView view);
Activity getActivity();
void hideActionBar();
void showActionBar();
void longClickPage(String url);
void openBookmarkPage(WebView view);
void showFileChooser(ValueCallback<Uri[]> filePathCallback);
void closeEmptyTab();
boolean proxyIsNotReady();
void updateBookmarkIndicator(String url);
}
@@ -0,0 +1,514 @@
package acr.browser.lightning.database;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.provider.Browser;
import android.support.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils;
public class BookmarkManager {
private final Context mContext;
private static final String TITLE = "title";
private static final String URL = "url";
private static final String FOLDER = "folder";
private static final String ORDER = "order";
private static final String FILE_BOOKMARKS = "bookmarks.dat";
private Set<String> mBookmarkSearchSet = new HashSet<>();
private final List<HistoryItem> mBookmarkList = new ArrayList<>();
private String mCurrentFolder = "";
private static BookmarkManager mInstance;
public static BookmarkManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new BookmarkManager(context);
}
return mInstance;
}
private BookmarkManager(Context context) {
mContext = context;
mBookmarkList.clear();
mBookmarkList.addAll(getAllBookmarks(true));
mBookmarkSearchSet = getBookmarkUrls(mBookmarkList);
}
public boolean isBookmark(String url) {
return mBookmarkSearchSet.contains(url);
}
/**
* This method adds the the HistoryItem item to permanent bookmark storage.
* It returns true if the operation was successful.
*
* @param item the item to add
*/
public synchronized boolean addBookmark(HistoryItem item) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
if (item.getUrl() == null || mBookmarkSearchSet.contains(item.getUrl())) {
return false;
}
mBookmarkList.add(item);
BufferedWriter bookmarkWriter = null;
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, true));
JSONObject object = new JSONObject();
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
mBookmarkSearchSet.add(item.getUrl());
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
return true;
}
/**
* This method adds the list of HistoryItems to permanent bookmark storage
*
* @param list the list of HistoryItems to add to bookmarks
*/
private synchronized void addBookmarkList(List<HistoryItem> list) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedWriter bookmarkWriter = null;
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, true));
JSONObject object = new JSONObject();
for (HistoryItem item : list) {
if (item.getUrl() != null && !mBookmarkSearchSet.contains(item.getUrl())) {
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
mBookmarkSearchSet.add(item.getUrl());
mBookmarkList.add(item);
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
}
/**
* This method deletes the bookmark with the given url. It returns
* true if the deletion was successful.
*
* @param deleteItem the bookmark item to delete
*/
public synchronized boolean deleteBookmark(HistoryItem deleteItem) {
if (deleteItem == null || deleteItem.isFolder()) {
return false;
}
mBookmarkSearchSet.remove(deleteItem.getUrl());
mBookmarkList.remove(deleteItem);
overwriteBookmarks(mBookmarkList);
return true;
}
/**
* renames a folder and moves all it's contents to that folder
*
* @param oldName the folder to be renamed
* @param newName the new name of the folder
*/
public synchronized void renameFolder(@NonNull String oldName, @NonNull String newName) {
if (newName.isEmpty()) {
return;
}
for (int n = 0; n < mBookmarkList.size(); n++) {
if (mBookmarkList.get(n).getFolder().equals(oldName)) {
mBookmarkList.get(n).setFolder(newName);
} else if (mBookmarkList.get(n).isFolder() && mBookmarkList.get(n).getTitle().equals(oldName)) {
mBookmarkList.get(n).setTitle(newName);
mBookmarkList.get(n).setUrl(Constants.FOLDER + newName);
}
}
overwriteBookmarks(mBookmarkList);
}
/**
* Delete the folder and move all bookmarks to the top level
*
* @param name the name of the folder to be deleted
*/
public synchronized void deleteFolder(@NonNull String name) {
Iterator<HistoryItem> iterator = mBookmarkList.iterator();
while (iterator.hasNext()) {
HistoryItem item = iterator.next();
if (!item.isFolder() && item.getFolder().equals(name)) {
item.setFolder("");
} else if (item.getTitle().equals(name)) {
iterator.remove();
}
}
overwriteBookmarks(mBookmarkList);
}
/**
* This method edits a particular bookmark in the bookmark database
*
* @param oldItem This is the old item that you wish to edit
* @param newItem This is the new item that will overwrite the old item
*/
public synchronized void editBookmark(HistoryItem oldItem, HistoryItem newItem) {
if (oldItem == null || newItem == null || oldItem.isFolder()) {
return;
}
mBookmarkList.remove(oldItem);
mBookmarkList.add(newItem);
if (!oldItem.getUrl().equals(newItem.getUrl())) {
// Update the BookmarkMap if the URL has been changed
mBookmarkSearchSet.remove(oldItem.getUrl());
mBookmarkSearchSet.add(newItem.getUrl());
}
if (newItem.getUrl().isEmpty()) {
deleteBookmark(oldItem);
return;
}
if (newItem.getTitle().isEmpty()) {
newItem.setTitle(mContext.getString(R.string.untitled));
}
overwriteBookmarks(mBookmarkList);
}
/**
* This method exports the stored bookmarks to a text file in the device's
* external download directory
*/
public synchronized void exportBookmarks(Activity activity) {
List<HistoryItem> bookmarkList = getAllBookmarks(true);
File bookmarksExport = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"BookmarksExport.txt");
int counter = 0;
while (bookmarksExport.exists()) {
counter++;
bookmarksExport = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"BookmarksExport-" + counter + ".txt");
}
BufferedWriter bookmarkWriter = null;
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksExport,
false));
JSONObject object = new JSONObject();
for (HistoryItem item : bookmarkList) {
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
}
Utils.showSnackbar(activity, activity.getString(R.string.bookmark_export_path)
+ ' ' + bookmarksExport.getPath());
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
}
/**
* This method returns a list of ALL stored bookmarks.
* This is a disk-bound operation and should not be
* done very frequently.
*
* @return returns a list of bookmarks that can be sorted
*/
public synchronized List<HistoryItem> getAllBookmarks(boolean sort) {
List<HistoryItem> bookmarks = new ArrayList<>();
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
try {
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile));
String line;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
HistoryItem item = new HistoryItem();
item.setTitle(object.getString(TITLE));
item.setUrl(object.getString(URL));
item.setFolder(object.getString(FOLDER));
item.setOrder(object.getInt(ORDER));
item.setImageId(R.drawable.ic_bookmark);
bookmarks.add(item);
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
}
if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase());
}
return bookmarks;
}
/**
* This method returns a list of bookmarks and folders located in the specified folder.
* This method should generally be used by the UI when it needs a list to display to the
* user as it returns a subset of all bookmarks and includes folders as well which are
* really 'fake' bookmarks.
*
* @param folder the name of the folder to retrieve bookmarks from
* @return a list of bookmarks found in that folder
*/
public synchronized List<HistoryItem> getBookmarksFromFolder(String folder, boolean sort) {
List<HistoryItem> bookmarks = new ArrayList<>();
if (folder == null || folder.isEmpty()) {
bookmarks.addAll(getFolders(sort));
folder = "";
}
mCurrentFolder = folder;
for (int n = 0; n < mBookmarkList.size(); n++) {
if (mBookmarkList.get(n).getFolder().equals(folder))
bookmarks.add(mBookmarkList.get(n));
}
if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase());
}
return bookmarks;
}
/**
* Tells you if you are at the root folder or in a subfolder
*
* @return returns true if you are in the root folder
*/
public boolean isRootFolder() {
return mCurrentFolder.isEmpty();
}
/**
* Method is used internally for searching the bookmarks
*
* @return a sorted map of all bookmarks, useful for seeing if a bookmark exists
*/
private Set<String> getBookmarkUrls(List<HistoryItem> list) {
Set<String> set = new HashSet<>();
for (int n = 0; n < list.size(); n++) {
if (!mBookmarkList.get(n).isFolder())
set.add(mBookmarkList.get(n).getUrl());
}
return set;
}
/**
* This method returns a list of all folders.
* Folders cannot be empty as they are generated from
* the list of bookmarks that have non-empty folder fields.
*
* @return a list of all folders
*/
public synchronized List<HistoryItem> getFolders(boolean sort) {
List<HistoryItem> folders = new ArrayList<>();
Set<String> folderMap = new HashSet<>();
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
try {
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile));
String line;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
String folderName = object.getString(FOLDER);
if (!folderName.isEmpty() && !folderMap.contains(folderName)) {
HistoryItem item = new HistoryItem();
item.setTitle(folderName);
item.setUrl(Constants.FOLDER + folderName);
item.setIsFolder(true);
folderMap.add(folderName);
folders.add(item);
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
}
if (sort) {
Collections.sort(folders, new SortIgnoreCase());
}
return folders;
}
/**
* returns a list of folder titles that can be used for suggestions in a
* simple list adapter
*
* @return a list of folder title strings
*/
public synchronized List<String> getFolderTitles() {
List<String> folders = new ArrayList<>();
Set<String> folderMap = new HashSet<>();
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
try {
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile));
String line;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
String folderName = object.getString(FOLDER);
if (!folderName.isEmpty() && !folderMap.contains(folderName)) {
folders.add(folderName);
folderMap.add(folderName);
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
}
return folders;
}
/**
* This method imports the bookmarks from a backup file that is located on
* external storage
*
* @param file the file to attempt to import bookmarks from
*/
public synchronized void importBookmarksFromFile(File file, Activity activity) {
if (file == null) {
return;
}
List<HistoryItem> list = new ArrayList<>();
BufferedReader bookmarksReader = null;
try {
bookmarksReader = new BufferedReader(new FileReader(file));
String line;
int number = 0;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
HistoryItem item = new HistoryItem();
item.setTitle(object.getString(TITLE));
item.setUrl(object.getString(URL));
item.setFolder(object.getString(FOLDER));
item.setOrder(object.getInt(ORDER));
list.add(item);
number++;
}
addBookmarkList(list);
String message = activity.getResources().getString(R.string.message_import);
Utils.showSnackbar(activity, number + " " + message);
} catch (IOException | JSONException e) {
e.printStackTrace();
Utils.createInformativeDialog(activity, R.string.title_error, R.string.import_bookmark_error);
} finally {
Utils.close(bookmarksReader);
}
}
/**
* This method overwrites the entire bookmark file with the list of
* bookmarks. This is useful when an edit has been made to one or more
* bookmarks in the list
*
* @param list the list of bookmarks to overwrite the old ones with
*/
private synchronized void overwriteBookmarks(List<HistoryItem> list) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedWriter bookmarkWriter = null;
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, false));
JSONObject object = new JSONObject();
for (int n = 0; n < list.size(); n++) {
HistoryItem item = list.get(n);
if (!item.isFolder()) {
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
}
/**
* find the index of a bookmark in a list using only its URL
*
* @param list the list to search
* @param url the url to compare
* @return returns the index of the bookmark or -1 if none was found
*/
public static int getIndexOfBookmark(final List<HistoryItem> list, final String url) {
for (int n = 0; n < list.size(); n++) {
if (list.get(n).getUrl().equals(url)) {
return n;
}
}
return -1;
}
/**
* This class sorts bookmarks alphabetically, with folders coming after bookmarks
*/
public static class SortIgnoreCase implements Comparator<HistoryItem> {
public int compare(HistoryItem o1, HistoryItem o2) {
if (o1 == null || o2 == null || o1.getTitle() == null || o2.getTitle() == null) {
return 0;
}
if (o1.isFolder() == o2.isFolder()) {
return o1.getTitle().toLowerCase(Locale.getDefault())
.compareTo(o2.getTitle().toLowerCase(Locale.getDefault()));
} else {
return o1.isFolder() ? 1 : -1;
}
}
}
private static final String[] DEV = {"https://twitter.com/RestainoAnthony", "The Developer"};
private static final String[] FACEBOOK = {"https://www.facebook.com/", "Facebook"};
private static final String[] TWITTER = {"https://twitter.com", "Twitter"};
private static final String[] GOOGLE = {"https://www.google.com/", "Google"};
private static final String[] YAHOO = {"https://www.yahoo.com/", "Yahoo"};
public static final String[][] DEFAULT_BOOKMARKS = {
DEV,
FACEBOOK,
TWITTER,
GOOGLE,
YAHOO
};
}
@@ -0,0 +1,209 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
import acr.browser.lightning.R;
public class HistoryDatabase extends SQLiteOpenHelper {
// All Static variables
// Database Version
private static final int DATABASE_VERSION = 2;
// Database Name
public static final String DATABASE_NAME = "historyManager";
// HistoryItems table name
private static final String TABLE_HISTORY = "history";
// HistoryItems Table Columns names
private static final String KEY_ID = "id";
private static final String KEY_URL = "url";
private static final String KEY_TITLE = "title";
private static final String KEY_TIME_VISITED = "time";
private SQLiteDatabase mDatabase;
private static HistoryDatabase mInstance;
public static HistoryDatabase getInstance(Context context) {
if (mInstance == null || mInstance.isClosed()) {
mInstance = new HistoryDatabase(context);
}
return mInstance;
}
private HistoryDatabase(Context context) {
super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION);
mDatabase = this.getWritableDatabase();
}
// Creating Tables
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_HISTORY_TABLE = "CREATE TABLE " + TABLE_HISTORY + '(' + KEY_ID
+ " INTEGER PRIMARY KEY," + KEY_URL + " TEXT," + KEY_TITLE + " TEXT,"
+ KEY_TIME_VISITED + " INTEGER" + ')';
db.execSQL(CREATE_HISTORY_TABLE);
}
// Upgrading database
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Drop older table if it exists
db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
// Create tables again
onCreate(db);
}
public void deleteHistory() {
mDatabase.delete(TABLE_HISTORY, null, null);
mDatabase.close();
mDatabase = this.getWritableDatabase();
}
public boolean isClosed() {
return mDatabase == null || !mDatabase.isOpen();
}
@Override
public synchronized void close() {
if (mDatabase != null) {
mDatabase.close();
}
super.close();
}
public synchronized void deleteHistoryItem(String url) {
mDatabase.delete(TABLE_HISTORY, KEY_URL + " = ?", new String[] { url });
}
public synchronized void visitHistoryItem(String url, String title) {
ContentValues values = new ContentValues();
values.put(KEY_TITLE, title);
values.put(KEY_TIME_VISITED, System.currentTimeMillis());
Cursor q = mDatabase.query(false, TABLE_HISTORY, new String[] { KEY_URL },
KEY_URL + " = ?", new String[] { url }, null, null, null, "1");
if (q.getCount() > 0) {
mDatabase.update(TABLE_HISTORY, values, KEY_URL + " = ?", new String[] { url });
} else {
addHistoryItem(new HistoryItem(url, title));
}
q.close();
}
private synchronized void addHistoryItem(HistoryItem item) {
ContentValues values = new ContentValues();
values.put(KEY_URL, item.getUrl());
values.put(KEY_TITLE, item.getTitle());
values.put(KEY_TIME_VISITED, System.currentTimeMillis());
mDatabase.insert(TABLE_HISTORY, null, values);
}
String getHistoryItem(String url) {
Cursor cursor = mDatabase.query(TABLE_HISTORY, new String[] { KEY_ID, KEY_URL, KEY_TITLE },
KEY_URL + " = ?", new String[] { url }, null, null, null, null);
String m = null;
if (cursor != null) {
cursor.moveToFirst();
m = cursor.getString(0);
cursor.close();
}
return m;
}
public List<HistoryItem> findItemsContaining(String search) {
List<HistoryItem> itemList = new ArrayList<>();
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " WHERE " + KEY_TITLE + " LIKE '%"
+ search + "%' OR " + KEY_URL + " LIKE '%" + search + "%' " + "ORDER BY "
+ KEY_TIME_VISITED + " DESC LIMIT 5";
Cursor cursor = mDatabase.rawQuery(selectQuery, null);
int n = 0;
if (cursor.moveToFirst()) {
do {
HistoryItem item = new HistoryItem();
item.setID(Integer.parseInt(cursor.getString(0)));
item.setUrl(cursor.getString(1));
item.setTitle(cursor.getString(2));
item.setImageId(R.drawable.ic_history);
itemList.add(item);
n++;
} while (cursor.moveToNext() && n < 5);
}
cursor.close();
return itemList;
}
public List<HistoryItem> getLastHundredItems() {
List<HistoryItem> itemList = new ArrayList<>();
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED
+ " DESC";
Cursor cursor = mDatabase.rawQuery(selectQuery, null);
int counter = 0;
if (cursor.moveToFirst()) {
do {
HistoryItem item = new HistoryItem();
item.setID(Integer.parseInt(cursor.getString(0)));
item.setUrl(cursor.getString(1));
item.setTitle(cursor.getString(2));
item.setImageId(R.drawable.ic_history);
itemList.add(item);
counter++;
} while (cursor.moveToNext() && counter < 100);
}
cursor.close();
return itemList;
}
public List<HistoryItem> getAllHistoryItems() {
List<HistoryItem> itemList = new ArrayList<>();
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED
+ " DESC";
Cursor cursor = mDatabase.rawQuery(selectQuery, null);
if (cursor.moveToFirst()) {
do {
HistoryItem item = new HistoryItem();
item.setID(Integer.parseInt(cursor.getString(0)));
item.setUrl(cursor.getString(1));
item.setTitle(cursor.getString(2));
item.setImageId(R.drawable.ic_history);
itemList.add(item);
} while (cursor.moveToNext());
}
cursor.close();
return itemList;
}
public synchronized int updateHistoryItem(HistoryItem item) {
ContentValues values = new ContentValues();
values.put(KEY_URL, item.getUrl());
values.put(KEY_TITLE, item.getTitle());
values.put(KEY_TIME_VISITED, System.currentTimeMillis());
return mDatabase.update(TABLE_HISTORY, values, KEY_ID + " = ?",
new String[] { String.valueOf(item.getId()) });
}
public int getHistoryItemsCount() {
String countQuery = "SELECT * FROM " + TABLE_HISTORY;
Cursor cursor = mDatabase.rawQuery(countQuery, null);
int n = cursor.getCount();
cursor.close();
return n;
}
}
@@ -0,0 +1,172 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.database;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
public class HistoryItem implements Comparable<HistoryItem> {
// private variables
private int mId = 0;
private String mUrl = "";
private String mTitle = "";
private String mFolder = "";
private Bitmap mBitmap = null;
private int mImageId = 0;
private int mOrder = 0;
private boolean mIsFolder = false;
// Empty constructor
public HistoryItem() {
}
public HistoryItem(HistoryItem item) {
this.mUrl = item.mUrl;
this.mTitle = item.mTitle;
this.mFolder = item.mFolder;
this.mOrder = item.mOrder;
this.mIsFolder = item.mIsFolder;
}
// constructor
public HistoryItem(int id, String url, String title) {
this.mId = id;
this.mUrl = url;
this.mTitle = title;
this.mBitmap = null;
}
// constructor
public HistoryItem(String url, String title) {
this.mUrl = url;
this.mTitle = title;
this.mBitmap = null;
}
// constructor
public HistoryItem(String url, String title, int imageId) {
this.mUrl = url;
this.mTitle = title;
this.mBitmap = null;
this.mImageId = imageId;
}
// getting ID
public int getId() {
return this.mId;
}
public int getImageId() {
return this.mImageId;
}
// setting id
public void setID(int id) {
this.mId = id;
}
public void setImageId(int id) {
this.mImageId = id;
}
public void setBitmap(Bitmap image) {
mBitmap = image;
}
public void setFolder(String folder) {
mFolder = (folder == null) ? "" : folder;
}
public void setOrder(int order) {
mOrder = order;
}
public int getOrder() {
return mOrder;
}
public String getFolder() {
return mFolder;
}
public Bitmap getBitmap() {
return mBitmap;
}
// getting name
public String getUrl() {
return this.mUrl;
}
// setting name
public void setUrl(String url) {
this.mUrl = (url == null) ? "" : url;
}
// getting phone number
public String getTitle() {
return this.mTitle;
}
// setting phone number
public void setTitle(String title) {
this.mTitle = (title == null) ? "" : title;
}
public void setIsFolder(boolean isFolder) {
mIsFolder = isFolder;
}
public boolean isFolder() {
return mIsFolder;
}
@Override
public String toString() {
return mTitle;
}
@Override
public int compareTo(@NonNull HistoryItem another) {
return mTitle.compareTo(another.mTitle);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || ((Object) this).getClass() != o.getClass()) {
return false;
}
HistoryItem that = (HistoryItem) o;
if (mId != that.mId) {
return false;
}
if (mImageId != that.mImageId) {
return false;
}
if (mBitmap != null ? !mBitmap.equals(that.mBitmap) : that.mBitmap != null) {
return false;
}
return mTitle.equals(that.mTitle) && mUrl.equals(that.mUrl);
}
@Override
public int hashCode() {
int result = mId;
result = 31 * result + mUrl.hashCode();
result = 31 * result + mTitle.hashCode();
result = 31 * result + (mBitmap != null ? mBitmap.hashCode() : 0);
result = 31 * result + mImageId;
return result;
}
}
@@ -0,0 +1,210 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.download;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.URLUtil;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils;
/**
* Handle download requests
*/
public class DownloadHandler {
private static final String LOGTAG = "DLHandler";
/**
* Notify the host application a download should be done, or that the data
* should be streamed if a streaming viewer is available.
*
* @param activity Activity requesting the download.
* @param url The full url to the content that should be downloaded
* @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server
*/
public static void onDownloadStart(Activity activity, String url, String userAgent,
String contentDisposition, String mimetype) {
// if we're dealing wih A/V content that's not explicitly marked
// for download, check if it's streamable.
if (contentDisposition == null
|| !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
// query the package manager to see if there's a registered handler
// that matches.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), mimetype);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ResolveInfo info = activity.getPackageManager().resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (info != null) {
ComponentName myName = activity.getComponentName();
// If we resolved to ourselves, we don't want to attempt to
// load the url only to try and download it again.
if (!myName.getPackageName().equals(info.activityInfo.packageName)
|| !myName.getClassName().equals(info.activityInfo.name)) {
// someone (other than us) knows how to handle this mime
// type with this scheme, don't download.
try {
activity.startActivity(intent);
return;
} catch (ActivityNotFoundException ex) {
// Best behavior is to fall back to a download in this
// case
}
}
}
}
onDownloadStartNoStream(activity, url, userAgent, contentDisposition, mimetype
);
}
// This is to work around the fact that java.net.URI throws Exceptions
// instead of just encoding URL's properly
// Helper method for onDownloadStartNoStream
private static String encodePath(String path) {
char[] chars = path.toCharArray();
boolean needed = false;
for (char c : chars) {
if (c == '[' || c == ']' || c == '|') {
needed = true;
break;
}
}
if (!needed) {
return path;
}
StringBuilder sb = new StringBuilder("");
for (char c : chars) {
if (c == '[' || c == ']' || c == '|') {
sb.append('%');
sb.append(Integer.toHexString(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Notify the host application a download should be done, even if there is a
* streaming viewer available for thise type.
*
* @param activity Activity requesting the download.
* @param url The full url to the content that should be downloaded
* @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server
*/
/* package */
private static void onDownloadStartNoStream(final Activity activity, String url, String userAgent,
String contentDisposition, String mimetype) {
String filename = URLUtil.guessFileName(url, contentDisposition, mimetype);
// Check to see if we have an SDCard
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED)) {
int title;
String msg;
// Check to see if the SDCard is busy, same as the music app
if (status.equals(Environment.MEDIA_SHARED)) {
msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);
title = R.string.download_sdcard_busy_dlg_title;
} else {
msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);
title = R.string.download_no_sdcard_dlg_title;
}
new AlertDialog.Builder(activity).setTitle(title)
.setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg)
.setPositiveButton(R.string.action_ok, null).show();
return;
}
// java.net.URI is a lot stricter than KURL so we have to encode some
// extra characters. Fix for b 2538060 and b 1634719
WebAddress webAddress;
try {
webAddress = new WebAddress(url);
webAddress.setPath(encodePath(webAddress.getPath()));
} catch (Exception e) {
// This only happens for very bad urls, we want to catch the
// exception here
Log.e(LOGTAG, "Exception while trying to parse url '" + url + '\'', e);
return;
}
String addressString = webAddress.toString();
Uri uri = Uri.parse(addressString);
final DownloadManager.Request request;
try {
request = new DownloadManager.Request(uri);
} catch (IllegalArgumentException e) {
Utils.showSnackbar(activity, R.string.cannot_download);
return;
}
request.setMimeType(mimetype);
// set downloaded file destination to /sdcard/Download.
// or, should it be set to one of several Environment.DIRECTORY* dirs
// depending on mimetype?
String location = PreferenceManager.getInstance().getDownloadDirectory();
request.setDestinationInExternalPublicDir(location, filename);
// let this downloaded file be scanned by MediaScanner - so that it can
// show up in Gallery app, for example.
request.allowScanningByMediaScanner();
request.setDescription(webAddress.getHost());
// XXX: Have to use the old url since the cookies were stored using the
// old percent-encoded url.
String cookies = CookieManager.getInstance().getCookie(url);
request.addRequestHeader("cookie", cookies);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (mimetype == null) {
if (TextUtils.isEmpty(addressString)) {
return;
}
// We must have long pressed on a link or image to download it. We
// are not sure of the mimetype in this case, so do a head request
new FetchUrlMimeType(activity, request, addressString, cookies, userAgent).start();
} else {
final DownloadManager manager = (DownloadManager) activity
.getSystemService(Context.DOWNLOAD_SERVICE);
new Thread("Browser download") {
@Override
public void run() {
try {
manager.enqueue(request);
} catch (IllegalArgumentException e) {
// Probably got a bad URL or something
e.printStackTrace();
Utils.showSnackbar(activity, R.string.cannot_download);
}
}
}.start();
Utils.showSnackbar(activity, R.string.download_pending);
}
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.download;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.os.Environment;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import acr.browser.lightning.R;
import acr.browser.lightning.utils.Utils;
/**
* This class is used to pull down the http headers of a given URL so that we
* can analyse the mimetype and make any correction needed before we give the
* URL to the download manager. This operation is needed when the user
* long-clicks on a link or image and we don't know the mimetype. If the user
* just clicks on the link, we will do the same steps of correcting the mimetype
* down in android.os.webkit.LoadListener rather than handling it here.
*/
public class FetchUrlMimeType extends Thread {
private final Context mContext;
private final DownloadManager.Request mRequest;
private final String mUri;
private final String mCookies;
private final String mUserAgent;
public FetchUrlMimeType(Activity activity, DownloadManager.Request request, String uri,
String cookies, String userAgent) {
mContext = activity.getApplicationContext();
mRequest = request;
mUri = uri;
mCookies = cookies;
mUserAgent = userAgent;
Utils.showSnackbar(activity, R.string.download_pending);
}
@Override
public void run() {
// User agent is likely to be null, though the AndroidHttpClient
// seems ok with that.
String mimeType = null;
String contentDisposition = null;
HttpURLConnection connection = null;
try {
URL url = new URL(mUri);
connection = (HttpURLConnection) url.openConnection();
if (mCookies != null && !mCookies.isEmpty()) {
connection.addRequestProperty("Cookie", mCookies);
connection.setRequestProperty("User-Agent", mUserAgent);
}
connection.connect();
// We could get a redirect here, but if we do lets let
// the download manager take care of it, and thus trust that
// the server sends the right mimetype
if (connection.getResponseCode() == 200) {
String header = connection.getHeaderField("Content-Type");
if (header != null) {
mimeType = header;
final int semicolonIndex = mimeType.indexOf(';');
if (semicolonIndex != -1) {
mimeType = mimeType.substring(0, semicolonIndex);
}
}
String contentDispositionHeader = connection.getHeaderField("Content-Disposition");
if (contentDispositionHeader != null) {
contentDisposition = contentDispositionHeader;
}
}
} catch (IllegalArgumentException | IOException ex) {
if (connection != null)
connection.disconnect();
} finally {
if (connection != null)
connection.disconnect();
}
if (mimeType != null) {
if (mimeType.equalsIgnoreCase("text/plain")
|| mimeType.equalsIgnoreCase("application/octet-stream")) {
String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(mUri));
if (newMimeType != null) {
mRequest.setMimeType(newMimeType);
}
}
String filename = URLUtil.guessFileName(mUri, contentDisposition, mimeType);
mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
}
// Start the download
DownloadManager manager = (DownloadManager) mContext
.getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(mRequest);
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.download;
import android.app.Activity;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
public class LightningDownloadListener implements DownloadListener {
private final Activity mActivity;
public LightningDownloadListener(Activity activity) {
mActivity = activity;
}
@Override
public void onDownloadStart(final String url, final String userAgent,
final String contentDisposition, final String mimetype, long contentLength) {
String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
DownloadHandler.onDownloadStart(mActivity, url, userAgent,
contentDisposition, mimetype);
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); // dialog
builder.setTitle(fileName)
.setMessage(mActivity.getResources().getString(R.string.dialog_download))
.setPositiveButton(mActivity.getResources().getString(R.string.action_download),
dialogClickListener)
.setNegativeButton(mActivity.getResources().getString(R.string.action_cancel),
dialogClickListener).show();
Log.i(Constants.TAG, "Downloading" + fileName);
}
}
@@ -0,0 +1,168 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.download;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static android.util.Patterns.GOOD_IRI_CHAR;
/**
* Web Address Parser
*
* This is called WebAddress, rather than URL or URI, because it attempts to
* parse the stuff that a user will actually type into a browser address widget.
*
* Unlike java.net.uri, this parser will not choke on URIs missing schemes. It
* will only throw a ParseException if the input is really hosed.
*
* If given an https scheme but no port, fills in port
*/
public class WebAddress {
private String mScheme;
private String mHost;
private int mPort;
private String mPath;
private String mAuthInfo;
static final int MATCH_GROUP_SCHEME = 1;
static final int MATCH_GROUP_AUTHORITY = 2;
static final int MATCH_GROUP_HOST = 3;
static final int MATCH_GROUP_PORT = 4;
static final int MATCH_GROUP_PATH = 5;
static final Pattern sAddressPattern = Pattern.compile(
/* scheme */"(?:(http|https|file)\\:\\/\\/)?" +
/* authority */"(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
/* host */"([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?" +
/* port */"(?:\\:([0-9]*))?" +
/* path */"(\\/?[^#]*)?" +
/* anchor */".*", Pattern.CASE_INSENSITIVE);
/**
* Parses given URI-like string.
*/
public WebAddress(String address) {
if (address == null) {
throw new IllegalArgumentException("address can't be null");
}
mScheme = "";
mHost = "";
mPort = -1;
mPath = "/";
mAuthInfo = "";
Matcher m = sAddressPattern.matcher(address);
String t;
if (!m.matches()) {
throw new IllegalArgumentException("Parsing of address '" + address + "' failed");
}
t = m.group(MATCH_GROUP_SCHEME);
if (t != null) {
mScheme = t.toLowerCase(Locale.ROOT);
}
t = m.group(MATCH_GROUP_AUTHORITY);
if (t != null) {
mAuthInfo = t;
}
t = m.group(MATCH_GROUP_HOST);
if (t != null) {
mHost = t;
}
t = m.group(MATCH_GROUP_PORT);
if (t != null && !t.isEmpty()) {
// The ':' character is not returned by the regex.
try {
mPort = Integer.parseInt(t);
} catch (NumberFormatException ex) {
throw new RuntimeException("Parsing of port number failed", ex);
}
}
t = m.group(MATCH_GROUP_PATH);
if (t != null && !t.isEmpty()) {
/*
* handle busted myspace frontpage redirect with missing initial "/"
*/
if (t.charAt(0) == '/') {
mPath = t;
} else {
mPath = '/' + t;
}
}
/*
* Get port from scheme or scheme from port, if necessary and possible
*/
if (mPort == 443 && mScheme != null && mScheme.isEmpty()) {
mScheme = "https";
} else if (mPort == -1) {
if ("https".equals(mScheme)) {
mPort = 443;
} else {
mPort = 80; // default
}
}
if (mScheme != null && mScheme.isEmpty()) {
mScheme = "http";
}
}
@Override
public String toString() {
String port = "";
if ((mPort != 443 && "https".equals(mScheme)) || (mPort != 80 && "http".equals(mScheme))) {
port = ':' + Integer.toString(mPort);
}
String authInfo = "";
if (!mAuthInfo.isEmpty()) {
authInfo = mAuthInfo + '@';
}
return mScheme + "://" + authInfo + mHost + port + mPath;
}
public void setScheme(String scheme) {
mScheme = scheme;
}
public String getScheme() {
return mScheme;
}
public void setHost(String host) {
mHost = host;
}
public String getHost() {
return mHost;
}
public void setPort(int port) {
mPort = port;
}
public int getPort() {
return mPort;
}
public void setPath(String path) {
mPath = path;
}
public String getPath() {
return mPath;
}
public void setAuthInfo(String authInfo) {
mAuthInfo = authInfo;
}
public String getAuthInfo() {
return mAuthInfo;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import acr.browser.lightning.R;
public class AboutSettingsFragment extends PreferenceFragment {
private Activity mActivity;
private static final String SETTINGS_VERSION = "pref_version";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_about);
mActivity = getActivity();
Preference version = findPreference(SETTINGS_VERSION);
version.setSummary(getVersion());
}
private String getVersion() {
try {
PackageInfo p = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(), 0);
return p.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "1.0";
}
}
}
@@ -0,0 +1,224 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import java.util.Arrays;
import java.util.List;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
public class AdvancedSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String SETTINGS_NEWWINDOW = "allow_new_window";
private static final String SETTINGS_ENABLECOOKIES = "allow_cookies";
private static final String SETTINGS_COOKIESINKOGNITO = "incognito_cookies";
private static final String SETTINGS_RESTORETABS = "restore_tabs";
private static final String SETTINGS_RENDERINGMODE = "rendering_mode";
private static final String SETTINGS_URLCONTENT = "url_contents";
private static final String SETTINGS_TEXTENCODING = "text_encoding";
private Activity mActivity;
private PreferenceManager mPreferences;
private CheckBoxPreference cbAllowPopups, cbenablecookies, cbcookiesInkognito, cbrestoreTabs;
private Preference renderingmode, urlcontent, textEncoding;
private CharSequence[] mUrlOptions;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_advanced);
mActivity = getActivity();
initPrefs();
}
private void initPrefs() {
// mPreferences storage
mPreferences = PreferenceManager.getInstance();
renderingmode = findPreference(SETTINGS_RENDERINGMODE);
textEncoding = findPreference(SETTINGS_TEXTENCODING);
urlcontent = findPreference(SETTINGS_URLCONTENT);
cbAllowPopups = (CheckBoxPreference) findPreference(SETTINGS_NEWWINDOW);
cbenablecookies = (CheckBoxPreference) findPreference(SETTINGS_ENABLECOOKIES);
cbcookiesInkognito = (CheckBoxPreference) findPreference(SETTINGS_COOKIESINKOGNITO);
cbrestoreTabs = (CheckBoxPreference) findPreference(SETTINGS_RESTORETABS);
renderingmode.setOnPreferenceClickListener(this);
textEncoding.setOnPreferenceClickListener(this);
urlcontent.setOnPreferenceClickListener(this);
cbAllowPopups.setOnPreferenceChangeListener(this);
cbenablecookies.setOnPreferenceChangeListener(this);
cbcookiesInkognito.setOnPreferenceChangeListener(this);
cbrestoreTabs.setOnPreferenceChangeListener(this);
switch (mPreferences.getRenderingMode()) {
case 0:
renderingmode.setSummary(getString(R.string.name_normal));
break;
case 1:
renderingmode.setSummary(getString(R.string.name_inverted));
break;
case 2:
renderingmode.setSummary(getString(R.string.name_grayscale));
break;
case 3:
renderingmode.setSummary(getString(R.string.name_inverted_grayscale));
break;
}
textEncoding.setSummary(mPreferences.getTextEncoding());
mUrlOptions = getResources().getStringArray(R.array.url_content_array);
int option = mPreferences.getUrlBoxContentChoice();
urlcontent.setSummary(mUrlOptions[option]);
cbAllowPopups.setChecked(mPreferences.getPopupsEnabled());
cbenablecookies.setChecked(mPreferences.getCookiesEnabled());
cbcookiesInkognito.setChecked(mPreferences.getIncognitoCookiesEnabled());
cbrestoreTabs.setChecked(mPreferences.getRestoreLostTabsEnabled());
}
@Override
public boolean onPreferenceClick(Preference preference) {
switch (preference.getKey()) {
case SETTINGS_RENDERINGMODE:
renderPicker();
return true;
case SETTINGS_URLCONTENT:
urlBoxPicker();
return true;
case SETTINGS_TEXTENCODING:
textEncodingPicker();
return true;
default:
return false;
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// switch preferences
switch (preference.getKey()) {
case SETTINGS_NEWWINDOW:
mPreferences.setPopupsEnabled((Boolean) newValue);
cbAllowPopups.setChecked((Boolean) newValue);
return true;
case SETTINGS_ENABLECOOKIES:
mPreferences.setCookiesEnabled((Boolean) newValue);
cbenablecookies.setChecked((Boolean) newValue);
return true;
case SETTINGS_COOKIESINKOGNITO:
mPreferences.setIncognitoCookiesEnabled((Boolean) newValue);
cbcookiesInkognito.setChecked((Boolean) newValue);
return true;
case SETTINGS_RESTORETABS:
mPreferences.setRestoreLostTabsEnabled((Boolean) newValue);
cbrestoreTabs.setChecked((Boolean) newValue);
return true;
default:
return false;
}
}
private void renderPicker() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.rendering_mode));
CharSequence[] chars = {mActivity.getString(R.string.name_normal),
mActivity.getString(R.string.name_inverted),
mActivity.getString(R.string.name_grayscale),
mActivity.getString(R.string.name_inverted_grayscale)};
int n = mPreferences.getRenderingMode();
picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setRenderingMode(which);
switch (which) {
case 0:
renderingmode.setSummary(getString(R.string.name_normal));
break;
case 1:
renderingmode.setSummary(getString(R.string.name_inverted));
break;
case 2:
renderingmode.setSummary(getString(R.string.name_grayscale));
break;
case 3:
renderingmode.setSummary(getString(R.string.name_inverted_grayscale));
break;
}
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void textEncodingPicker() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.text_encoding));
final List<String> textEncodingList = Arrays.asList(Constants.TEXT_ENCODINGS);
int n = textEncodingList.indexOf(mPreferences.getTextEncoding());
picker.setSingleChoiceItems(Constants.TEXT_ENCODINGS, n, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setTextEncoding(Constants.TEXT_ENCODINGS[which]);
textEncoding.setSummary(Constants.TEXT_ENCODINGS[which]);
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void urlBoxPicker() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.url_contents));
int n = mPreferences.getUrlBoxContentChoice();
picker.setSingleChoiceItems(mUrlOptions, n, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setUrlBoxContentChoice(which);
if (which < mUrlOptions.length) {
urlcontent.setSummary(mUrlOptions[which]);
}
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
}
@@ -0,0 +1,161 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.Manifest;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import acr.browser.lightning.R;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.utils.PermissionsManager;
public class BookmarkSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener {
private static final String SETTINGS_EXPORT = "export_bookmark";
private static final String SETTINGS_IMPORT = "import_bookmark";
private Activity mActivity;
private BookmarkManager mBookmarkManager;
private File[] mFileList;
private String[] mFileNameList;
private PermissionsManager mPermissionsManager;
private static String[] REQUIRED_PERMISSIONS = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private static final File mPath = new File(Environment.getExternalStorageDirectory().toString());
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_bookmarks);
mActivity = getActivity();
mBookmarkManager = BookmarkManager.getInstance(mActivity.getApplicationContext());
initPrefs();
mPermissionsManager = PermissionsManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mPermissionsManager.requestPermissionsIfNecessary(getActivity(), REQUIRED_PERMISSIONS);
}
}
private void initPrefs() {
Preference exportpref = findPreference(SETTINGS_EXPORT);
Preference importpref = findPreference(SETTINGS_IMPORT);
exportpref.setOnPreferenceClickListener(this);
importpref.setOnPreferenceClickListener(this);
}
@Override
public boolean onPreferenceClick(Preference preference) {
switch (preference.getKey()) {
case SETTINGS_EXPORT:
if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) {
mBookmarkManager.exportBookmarks(getActivity());
}
return true;
case SETTINGS_IMPORT:
if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) {
loadFileList(null);
createDialog();
}
return true;
default:
return false;
}
}
private void loadFileList(File path) {
File file;
if (path != null) {
file = path;
} else {
file = mPath;
}
try {
file.mkdirs();
} catch (SecurityException e) {
e.printStackTrace();
}
if (file.exists()) {
mFileList = file.listFiles();
} else {
mFileList = new File[0];
}
if (mFileList == null) {
mFileNameList = new String[0];
mFileList = new File[0];
} else {
Arrays.sort(mFileList, new SortName());
mFileNameList = new String[mFileList.length];
}
for (int n = 0; n < mFileList.length; n++) {
mFileNameList[n] = mFileList[n].getName();
}
}
private class SortName implements Comparator<File> {
@Override
public int compare(File a, File b) {
if (a.isDirectory() && b.isDirectory())
return a.getName().compareTo(b.getName());
if (a.isDirectory())
return -1;
if (b.isDirectory())
return 1;
if (a.isFile() && b.isFile())
return a.getName().compareTo(b.getName());
else
return 1;
}
}
private void createDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
final String title = getString(R.string.title_chooser);
builder.setTitle(title + ": " + Environment.getExternalStorageDirectory());
if (mFileList == null) {
builder.show();
}
builder.setItems(mFileNameList, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mFileList[which].isDirectory()) {
builder.setTitle(title + ": " + mFileList[which]);
loadFileList(mFileList[which]);
builder.setItems(mFileNameList, this);
builder.show();
} else {
mBookmarkManager.importBookmarksFromFile(mFileList[which], getActivity());
}
}
});
builder.show();
}
}
@@ -0,0 +1,228 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
public class DisplaySettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String SETTINGS_HIDESTATUSBAR = "fullScreenOption";
private static final String SETTINGS_FULLSCREEN = "fullscreen";
private static final String SETTINGS_VIEWPORT = "wideViewPort";
private static final String SETTINGS_OVERVIEWMODE = "overViewMode";
private static final String SETTINGS_REFLOW = "text_reflow";
private static final String SETTINGS_THEME = "app_theme";
private static final String SETTINGS_TEXTSIZE = "text_size";
private static final float XXLARGE = 30.0f;
private static final float XLARGE = 26.0f;
private static final float LARGE = 22.0f;
private static final float MEDIUM = 18.0f;
private static final float SMALL = 14.0f;
private static final float XSMALL = 10.0f;
private Activity mActivity;
private PreferenceManager mPreferences;
private CheckBoxPreference cbstatus, cbfullscreen, cbviewport, cboverview, cbreflow;
private Preference theme;
private String[] mThemeOptions;
private int mCurrentTheme;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_display);
mActivity = getActivity();
initPrefs();
}
private void initPrefs() {
// mPreferences storage
mPreferences = PreferenceManager.getInstance();
mThemeOptions = this.getResources().getStringArray(R.array.themes);
mCurrentTheme = mPreferences.getUseTheme();
theme = findPreference(SETTINGS_THEME);
Preference textsize = findPreference(SETTINGS_TEXTSIZE);
cbstatus = (CheckBoxPreference) findPreference(SETTINGS_HIDESTATUSBAR);
cbfullscreen = (CheckBoxPreference) findPreference(SETTINGS_FULLSCREEN);
cbviewport = (CheckBoxPreference) findPreference(SETTINGS_VIEWPORT);
cboverview = (CheckBoxPreference) findPreference(SETTINGS_OVERVIEWMODE);
cbreflow = (CheckBoxPreference) findPreference(SETTINGS_REFLOW);
theme.setOnPreferenceClickListener(this);
textsize.setOnPreferenceClickListener(this);
cbstatus.setOnPreferenceChangeListener(this);
cbfullscreen.setOnPreferenceChangeListener(this);
cbviewport.setOnPreferenceChangeListener(this);
cboverview.setOnPreferenceChangeListener(this);
cbreflow.setOnPreferenceChangeListener(this);
cbstatus.setChecked(mPreferences.getHideStatusBarEnabled());
cbfullscreen.setChecked(mPreferences.getFullScreenEnabled());
cbviewport.setChecked(mPreferences.getUseWideViewportEnabled());
cboverview.setChecked(mPreferences.getOverviewModeEnabled());
cbreflow.setChecked(mPreferences.getTextReflowEnabled());
theme.setSummary(mThemeOptions[mPreferences.getUseTheme()]);
}
@Override
public boolean onPreferenceClick(Preference preference) {
switch (preference.getKey()) {
case SETTINGS_THEME:
themePicker();
return true;
case SETTINGS_TEXTSIZE:
textSizePicker();
return true;
default:
return false;
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// switch preferences
switch (preference.getKey()) {
case SETTINGS_HIDESTATUSBAR:
mPreferences.setHideStatusBarEnabled((Boolean) newValue);
cbstatus.setChecked((Boolean) newValue);
return true;
case SETTINGS_FULLSCREEN:
mPreferences.setFullScreenEnabled((Boolean) newValue);
cbfullscreen.setChecked((Boolean) newValue);
return true;
case SETTINGS_VIEWPORT:
mPreferences.setUseWideViewportEnabled((Boolean) newValue);
cbviewport.setChecked((Boolean) newValue);
return true;
case SETTINGS_OVERVIEWMODE:
mPreferences.setOverviewModeEnabled((Boolean) newValue);
cboverview.setChecked((Boolean) newValue);
return true;
case SETTINGS_REFLOW:
mPreferences.setTextReflowEnabled((Boolean) newValue);
cbreflow.setChecked((Boolean) newValue);
return true;
default:
return false;
}
}
private void textSizePicker() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
LinearLayout view = (LinearLayout) inflater.inflate(R.layout.seek_layout, null);
final SeekBar bar = (SeekBar) view.findViewById(R.id.text_size_seekbar);
final TextView sample = new TextView(getActivity());
sample.setText(R.string.untitled);
sample.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT));
sample.setGravity(Gravity.CENTER_HORIZONTAL);
view.addView(sample);
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar view, int size, boolean user) {
sample.setTextSize(getTextSize(size));
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {
}
@Override
public void onStopTrackingTouch(SeekBar arg0) {
}
});
final int MAX = 5;
bar.setMax(MAX);
bar.setProgress(MAX - mPreferences.getTextSize());
builder.setView(view);
builder.setTitle(R.string.title_text_size);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
mPreferences.setTextSize(MAX - bar.getProgress());
}
});
builder.show();
}
private float getTextSize(int size) {
switch (size) {
case 0:
return XSMALL;
case 1:
return SMALL;
case 2:
return MEDIUM;
case 3:
return LARGE;
case 4:
return XLARGE;
case 5:
return XXLARGE;
default:
return MEDIUM;
}
}
private void themePicker() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.theme));
int n = mPreferences.getUseTheme();
picker.setSingleChoiceItems(mThemeOptions, n, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setUseTheme(which);
if (which < mThemeOptions.length) {
theme.setSummary(mThemeOptions[which]);
}
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mCurrentTheme != mPreferences.getUseTheme()) {
getActivity().onBackPressed();
}
}
});
picker.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (mCurrentTheme != mPreferences.getUseTheme()) {
getActivity().onBackPressed();
}
}
});
picker.show();
}
}
@@ -0,0 +1,622 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import android.text.InputFilter;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.utils.Utils;
public class GeneralSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String SETTINGS_PROXY = "proxy";
private static final String SETTINGS_FLASH = "cb_flash";
private static final String SETTINGS_ADS = "cb_ads";
private static final String SETTINGS_IMAGES = "cb_images";
private static final String SETTINGS_JAVASCRIPT = "cb_javascript";
private static final String SETTINGS_COLORMODE = "cb_colormode";
private static final String SETTINGS_USERAGENT = "agent";
private static final String SETTINGS_DOWNLOAD = "download";
private static final String SETTINGS_HOME = "home";
private static final String SETTINGS_SEARCHENGINE = "search";
private static final String SETTINGS_GOOGLESUGGESTIONS = "google_suggestions";
private static final String SETTINGS_DRAWERTABS = "cb_drawertabs";
private Activity mActivity;
private static final int API = android.os.Build.VERSION.SDK_INT;
private PreferenceManager mPreferences;
private CharSequence[] mProxyChoices;
private Preference proxy, useragent, downloadloc, home, searchengine;
private String mDownloadLocation;
private int mAgentChoice;
private String mHomepage;
private CheckBoxPreference cbFlash, cbAds, cbImages, cbJsScript, cbColorMode, cbgooglesuggest, cbDrawerTabs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_general);
mActivity = getActivity();
initPrefs();
}
private void initPrefs() {
// mPreferences storage
mPreferences = PreferenceManager.getInstance();
proxy = findPreference(SETTINGS_PROXY);
useragent = findPreference(SETTINGS_USERAGENT);
downloadloc = findPreference(SETTINGS_DOWNLOAD);
home = findPreference(SETTINGS_HOME);
searchengine = findPreference(SETTINGS_SEARCHENGINE);
cbFlash = (CheckBoxPreference) findPreference(SETTINGS_FLASH);
cbAds = (CheckBoxPreference) findPreference(SETTINGS_ADS);
cbImages = (CheckBoxPreference) findPreference(SETTINGS_IMAGES);
cbJsScript = (CheckBoxPreference) findPreference(SETTINGS_JAVASCRIPT);
cbColorMode = (CheckBoxPreference) findPreference(SETTINGS_COLORMODE);
cbgooglesuggest = (CheckBoxPreference) findPreference(SETTINGS_GOOGLESUGGESTIONS);
cbDrawerTabs = (CheckBoxPreference) findPreference(SETTINGS_DRAWERTABS);
proxy.setOnPreferenceClickListener(this);
useragent.setOnPreferenceClickListener(this);
downloadloc.setOnPreferenceClickListener(this);
home.setOnPreferenceClickListener(this);
searchengine.setOnPreferenceClickListener(this);
cbFlash.setOnPreferenceChangeListener(this);
cbAds.setOnPreferenceChangeListener(this);
cbImages.setOnPreferenceChangeListener(this);
cbJsScript.setOnPreferenceChangeListener(this);
cbColorMode.setOnPreferenceChangeListener(this);
cbgooglesuggest.setOnPreferenceChangeListener(this);
cbDrawerTabs.setOnPreferenceChangeListener(this);
mAgentChoice = mPreferences.getUserAgentChoice();
mHomepage = mPreferences.getHomepage();
mDownloadLocation = mPreferences.getDownloadDirectory();
mProxyChoices = getResources().getStringArray(R.array.proxy_choices_array);
int choice = mPreferences.getProxyChoice();
if (choice == Constants.PROXY_MANUAL) {
proxy.setSummary(mPreferences.getProxyHost() + ':' + mPreferences.getProxyPort());
} else {
proxy.setSummary(mProxyChoices[choice]);
}
if (API >= 19) {
mPreferences.setFlashSupport(0);
}
setSearchEngineSummary(mPreferences.getSearchChoice());
downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' + mDownloadLocation);
if (mHomepage.contains("about:home")) {
home.setSummary(getResources().getString(R.string.action_homepage));
} else if (mHomepage.contains("about:blank")) {
home.setSummary(getResources().getString(R.string.action_blank));
} else if (mHomepage.contains("about:bookmarks")) {
home.setSummary(getResources().getString(R.string.action_bookmarks));
} else {
home.setSummary(mHomepage);
}
switch (mAgentChoice) {
case 1:
useragent.setSummary(getResources().getString(R.string.agent_default));
break;
case 2:
useragent.setSummary(getResources().getString(R.string.agent_desktop));
break;
case 3:
useragent.setSummary(getResources().getString(R.string.agent_mobile));
break;
case 4:
useragent.setSummary(getResources().getString(R.string.agent_custom));
}
int flashNum = mPreferences.getFlashSupport();
boolean imagesBool = mPreferences.getBlockImagesEnabled();
boolean enableJSBool = mPreferences.getJavaScriptEnabled();
proxy.setEnabled(Constants.FULL_VERSION);
cbAds.setEnabled(Constants.FULL_VERSION);
cbFlash.setEnabled(API < 19);
cbImages.setChecked(imagesBool);
cbJsScript.setChecked(enableJSBool);
cbFlash.setChecked(flashNum > 0);
cbAds.setChecked(Constants.FULL_VERSION && mPreferences.getAdBlockEnabled());
cbColorMode.setChecked(mPreferences.getColorModeEnabled());
cbgooglesuggest.setChecked(mPreferences.getGoogleSearchSuggestionsEnabled());
cbDrawerTabs.setChecked(mPreferences.getShowTabsInDrawer(true));
}
private void searchUrlPicker() {
final AlertDialog.Builder urlPicker = new AlertDialog.Builder(mActivity);
urlPicker.setTitle(getResources().getString(R.string.custom_url));
final EditText getSearchUrl = new EditText(mActivity);
String mSearchUrl = mPreferences.getSearchUrl();
getSearchUrl.setText(mSearchUrl);
urlPicker.setView(getSearchUrl);
urlPicker.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = getSearchUrl.getText().toString();
mPreferences.setSearchUrl(text);
searchengine.setSummary(getResources().getString(R.string.custom_url) + ": "
+ text);
}
});
urlPicker.show();
}
private void getFlashChoice() {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(mActivity.getResources().getString(R.string.title_flash));
builder.setMessage(getResources().getString(R.string.flash))
.setCancelable(true)
.setPositiveButton(getResources().getString(R.string.action_manual),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
mPreferences.setFlashSupport(1);
}
})
.setNegativeButton(getResources().getString(R.string.action_auto),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setFlashSupport(2);
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mPreferences.setFlashSupport(0);
}
});
AlertDialog alert = builder.create();
alert.show();
}
private void proxyChoicePicker() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.http_proxy));
picker.setSingleChoiceItems(mProxyChoices, mPreferences.getProxyChoice(),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setProxyChoice(which);
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void setProxyChoice(int choice) {
ProxyUtils utils = ProxyUtils.getInstance(mActivity);
switch (choice) {
case Constants.PROXY_ORBOT:
choice = utils.setProxyChoice(choice, mActivity);
break;
case Constants.PROXY_I2P:
choice = utils.setProxyChoice(choice, mActivity);
break;
case Constants.PROXY_MANUAL:
manualProxyPicker();
break;
}
mPreferences.setProxyChoice(choice);
if (choice < mProxyChoices.length)
proxy.setSummary(mProxyChoices[choice]);
}
private void manualProxyPicker() {
View v = mActivity.getLayoutInflater().inflate(R.layout.picker_manual_proxy, null);
final EditText eProxyHost = (EditText) v.findViewById(R.id.proxyHost);
final EditText eProxyPort = (EditText) v.findViewById(R.id.proxyPort);
// Limit the number of characters since the port needs to be of type int
// Use input filters to limite the EditText length and determine the max
// length by using length of integer MAX_VALUE
int maxCharacters = Integer.toString(Integer.MAX_VALUE).length();
InputFilter[] filterArray = new InputFilter[1];
filterArray[0] = new InputFilter.LengthFilter(maxCharacters - 1);
eProxyPort.setFilters(filterArray);
eProxyHost.setText(mPreferences.getProxyHost());
eProxyPort.setText(Integer.toString(mPreferences.getProxyPort()));
new AlertDialog.Builder(mActivity)
.setTitle(R.string.manual_proxy)
.setView(v)
.setPositiveButton(R.string.action_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String proxyHost = eProxyHost.getText().toString();
int proxyPort;
try {
// Try/Catch in case the user types an empty string or a number
// larger than max integer
proxyPort = Integer.parseInt(eProxyPort.getText().toString());
} catch (NumberFormatException ignored) {
proxyPort = mPreferences.getProxyPort();
}
mPreferences.setProxyHost(proxyHost);
mPreferences.setProxyPort(proxyPort);
proxy.setSummary(proxyHost + ':' + proxyPort);
}
}).show();
}
private void searchDialog() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.title_search_engine));
CharSequence[] chars = {getResources().getString(R.string.custom_url), "Google",
"Ask", "Bing", "Yahoo", "StartPage", "StartPage (Mobile)",
"DuckDuckGo (Privacy)", "DuckDuckGo Lite (Privacy)", "Baidu (Chinese)",
"Yandex (Russian)"};
int n = mPreferences.getSearchChoice();
picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setSearchChoice(which);
setSearchEngineSummary(which);
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void homepageDialog() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.home));
mHomepage = mPreferences.getHomepage();
int n;
if (mHomepage.contains("about:home")) {
n = 1;
} else if (mHomepage.contains("about:blank")) {
n = 2;
} else if (mHomepage.contains("about:bookmarks")) {
n = 3;
} else {
n = 4;
}
picker.setSingleChoiceItems(R.array.homepage, n - 1,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which + 1) {
case 1:
mPreferences.setHomepage("about:home");
home.setSummary(getResources().getString(R.string.action_homepage));
break;
case 2:
mPreferences.setHomepage("about:blank");
home.setSummary(getResources().getString(R.string.action_blank));
break;
case 3:
mPreferences.setHomepage("about:bookmarks");
home.setSummary(getResources().getString(R.string.action_bookmarks));
break;
case 4:
homePicker();
break;
}
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void homePicker() {
final AlertDialog.Builder homePicker = new AlertDialog.Builder(mActivity);
homePicker.setTitle(getResources().getString(R.string.title_custom_homepage));
final EditText getHome = new EditText(mActivity);
mHomepage = mPreferences.getHomepage();
if (!mHomepage.startsWith("about:")) {
getHome.setText(mHomepage);
} else {
getHome.setText("http://www.google.com");
}
homePicker.setView(getHome);
homePicker.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = getHome.getText().toString();
mPreferences.setHomepage(text);
home.setSummary(text);
}
});
homePicker.show();
}
private void downloadLocDialog() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.title_download_location));
mDownloadLocation = mPreferences.getDownloadDirectory();
int n;
if (mDownloadLocation.contains(Environment.DIRECTORY_DOWNLOADS)) {
n = 1;
} else {
n = 2;
}
picker.setSingleChoiceItems(R.array.download_folder, n - 1,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which + 1) {
case 1:
mPreferences.setDownloadDirectory(Environment.DIRECTORY_DOWNLOADS);
downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/'
+ Environment.DIRECTORY_DOWNLOADS);
break;
case 2:
downPicker();
break;
}
}
});
picker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
picker.show();
}
private void agentDialog() {
AlertDialog.Builder agentPicker = new AlertDialog.Builder(mActivity);
agentPicker.setTitle(getResources().getString(R.string.title_user_agent));
mAgentChoice = mPreferences.getUserAgentChoice();
agentPicker.setSingleChoiceItems(R.array.user_agent, mAgentChoice - 1,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPreferences.setUserAgentChoice(which + 1);
switch (which + 1) {
case 1:
useragent.setSummary(getResources().getString(R.string.agent_default));
break;
case 2:
useragent.setSummary(getResources().getString(R.string.agent_desktop));
break;
case 3:
useragent.setSummary(getResources().getString(R.string.agent_mobile));
break;
case 4:
useragent.setSummary(getResources().getString(R.string.agent_custom));
agentPicker();
break;
}
}
});
agentPicker.setNeutralButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
agentPicker.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
Log.i("Cancelled", "");
}
});
agentPicker.show();
}
private void agentPicker() {
final AlertDialog.Builder agentStringPicker = new AlertDialog.Builder(mActivity);
agentStringPicker.setTitle(getResources().getString(R.string.title_user_agent));
final EditText getAgent = new EditText(mActivity);
agentStringPicker.setView(getAgent);
agentStringPicker.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = getAgent.getText().toString();
mPreferences.setUserAgentString(text);
useragent.setSummary(getResources().getString(R.string.agent_custom));
}
});
agentStringPicker.show();
}
private void downPicker() {
final AlertDialog.Builder downLocationPicker = new AlertDialog.Builder(mActivity);
LinearLayout layout = new LinearLayout(mActivity);
downLocationPicker.setTitle(getResources().getString(R.string.title_download_location));
final EditText getDownload = new EditText(mActivity);
getDownload.setText(mPreferences.getDownloadDirectory());
int padding = Utils.dpToPx(10);
TextView v = new TextView(mActivity);
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
v.setTextColor(Color.DKGRAY);
v.setText(Constants.EXTERNAL_STORAGE + '/');
v.setPadding(padding, padding, 0, padding);
layout.addView(v);
layout.addView(getDownload);
if (API < Build.VERSION_CODES.JELLY_BEAN) {
layout.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.edit_text));
} else {
Drawable drawable;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable = getResources().getDrawable(android.R.drawable.edit_text, getActivity().getTheme());
} else {
drawable = getResources().getDrawable(android.R.drawable.edit_text);
}
layout.setBackground(drawable);
}
downLocationPicker.setView(layout);
downLocationPicker.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = getDownload.getText().toString();
mPreferences.setDownloadDirectory(text);
downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' + text);
}
});
downLocationPicker.show();
}
private void setSearchEngineSummary(int which) {
switch (which) {
case 0:
searchUrlPicker();
break;
case 1:
searchengine.setSummary("Google");
break;
case 2:
searchengine.setSummary("Ask");
break;
case 3:
searchengine.setSummary("Bing");
break;
case 4:
searchengine.setSummary("Yahoo");
break;
case 5:
searchengine.setSummary("StartPage");
break;
case 6:
searchengine.setSummary("StartPage (Mobile)");
break;
case 7:
searchengine.setSummary("DuckDuckGo");
break;
case 8:
searchengine.setSummary("DuckDuckGo Lite");
break;
case 9:
searchengine.setSummary("Baidu");
break;
case 10:
searchengine.setSummary("Yandex");
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
switch (preference.getKey()) {
case SETTINGS_PROXY:
proxyChoicePicker();
return true;
case SETTINGS_USERAGENT:
agentDialog();
return true;
case SETTINGS_DOWNLOAD:
downloadLocDialog();
return true;
case SETTINGS_HOME:
homepageDialog();
return true;
case SETTINGS_SEARCHENGINE:
searchDialog();
return true;
default:
return false;
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// switch preferences
switch (preference.getKey()) {
case SETTINGS_FLASH:
if (cbFlash.isChecked()) {
getFlashChoice();
} else {
mPreferences.setFlashSupport(0);
}
if (!Utils.isFlashInstalled(mActivity) && cbFlash.isChecked()) {
Utils.createInformativeDialog(mActivity, R.string.title_warning, R.string.dialog_adobe_not_installed);
cbFlash.setEnabled(false);
mPreferences.setFlashSupport(0);
}
cbFlash.setChecked((Boolean) newValue);
return true;
case SETTINGS_ADS:
mPreferences.setAdBlockEnabled((Boolean) newValue);
cbAds.setChecked((Boolean) newValue);
return true;
case SETTINGS_IMAGES:
mPreferences.setBlockImagesEnabled((Boolean) newValue);
cbImages.setChecked((Boolean) newValue);
return true;
case SETTINGS_JAVASCRIPT:
mPreferences.setJavaScriptEnabled((Boolean) newValue);
cbJsScript.setChecked((Boolean) newValue);
return true;
case SETTINGS_COLORMODE:
mPreferences.setColorModeEnabled((Boolean) newValue);
cbColorMode.setChecked((Boolean) newValue);
return true;
case SETTINGS_GOOGLESUGGESTIONS:
mPreferences.setGoogleSearchSuggestionsEnabled((Boolean) newValue);
cbgooglesuggest.setChecked((Boolean) newValue);
return true;
case SETTINGS_DRAWERTABS:
mPreferences.setShowTabsInDrawer((Boolean) newValue);
cbDrawerTabs.setChecked((Boolean) newValue);
default:
return false;
}
}
}
@@ -0,0 +1,247 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import android.webkit.WebView;
import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils;
import acr.browser.lightning.utils.WebUtils;
public class PrivacySettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String SETTINGS_LOCATION = "location";
private static final String SETTINGS_THIRDPCOOKIES = "third_party";
private static final String SETTINGS_SAVEPASSWORD = "password";
private static final String SETTINGS_CACHEEXIT = "clear_cache_exit";
private static final String SETTINGS_HISTORYEXIT = "clear_history_exit";
private static final String SETTINGS_COOKIEEXIT = "clear_cookies_exit";
private static final String SETTINGS_CLEARCACHE = "clear_cache";
private static final String SETTINGS_CLEARHISTORY = "clear_history";
private static final String SETTINGS_CLEARCOOKIES = "clear_cookies";
private static final String SETTINGS_CLEARWEBSTORAGE = "clear_webstorage";
private static final String SETTINGS_WEBSTORAGEEXIT = "clear_webstorage_exit";
private Activity mActivity;
private PreferenceManager mPreferences;
private CheckBoxPreference cblocation, cb3cookies, cbsavepasswords, cbcacheexit, cbhistoryexit,
cbcookiesexit, cbwebstorageexit;
private Handler messageHandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_privacy);
mActivity = getActivity();
initPrefs();
}
private void initPrefs() {
// mPreferences storage
mPreferences = PreferenceManager.getInstance();
Preference clearcache = findPreference(SETTINGS_CLEARCACHE);
Preference clearhistory = findPreference(SETTINGS_CLEARHISTORY);
Preference clearcookies = findPreference(SETTINGS_CLEARCOOKIES);
Preference clearwebstorage = findPreference(SETTINGS_CLEARWEBSTORAGE);
cblocation = (CheckBoxPreference) findPreference(SETTINGS_LOCATION);
cb3cookies = (CheckBoxPreference) findPreference(SETTINGS_THIRDPCOOKIES);
cbsavepasswords = (CheckBoxPreference) findPreference(SETTINGS_SAVEPASSWORD);
cbcacheexit = (CheckBoxPreference) findPreference(SETTINGS_CACHEEXIT);
cbhistoryexit = (CheckBoxPreference) findPreference(SETTINGS_HISTORYEXIT);
cbcookiesexit = (CheckBoxPreference) findPreference(SETTINGS_COOKIEEXIT);
cbwebstorageexit = (CheckBoxPreference) findPreference(SETTINGS_WEBSTORAGEEXIT);
clearcache.setOnPreferenceClickListener(this);
clearhistory.setOnPreferenceClickListener(this);
clearcookies.setOnPreferenceClickListener(this);
clearwebstorage.setOnPreferenceClickListener(this);
cblocation.setOnPreferenceChangeListener(this);
cb3cookies.setOnPreferenceChangeListener(this);
cbsavepasswords.setOnPreferenceChangeListener(this);
cbcacheexit.setOnPreferenceChangeListener(this);
cbhistoryexit.setOnPreferenceChangeListener(this);
cbcookiesexit.setOnPreferenceChangeListener(this);
cbwebstorageexit.setOnPreferenceChangeListener(this);
cblocation.setChecked(mPreferences.getLocationEnabled());
cbsavepasswords.setChecked(mPreferences.getSavePasswordsEnabled());
cbcacheexit.setChecked(mPreferences.getClearCacheExit());
cbhistoryexit.setChecked(mPreferences.getClearHistoryExitEnabled());
cbcookiesexit.setChecked(mPreferences.getClearCookiesExitEnabled());
cb3cookies.setChecked(mPreferences.getBlockThirdPartyCookiesEnabled());
cbwebstorageexit.setChecked(mPreferences.getClearWebStorageExitEnabled());
cb3cookies.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
messageHandler = new MessageHandler(mActivity);
}
private static class MessageHandler extends Handler {
final Activity mHandlerContext;
public MessageHandler(Activity context) {
this.mHandlerContext = context;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Utils.showSnackbar(mHandlerContext, R.string.message_clear_history);
break;
case 2:
Utils.showSnackbar(mHandlerContext, R.string.message_cookies_cleared);
break;
}
super.handleMessage(msg);
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
switch (preference.getKey()) {
case SETTINGS_CLEARCACHE:
clearCache();
return true;
case SETTINGS_CLEARHISTORY:
clearHistoryDialog();
return true;
case SETTINGS_CLEARCOOKIES:
clearCookiesDialog();
return true;
case SETTINGS_CLEARWEBSTORAGE:
clearWebStorage();
return true;
default:
return false;
}
}
private void clearHistoryDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(getResources().getString(R.string.title_clear_history));
builder.setMessage(getResources().getString(R.string.dialog_history))
.setPositiveButton(getResources().getString(R.string.action_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Thread clear = new Thread(new Runnable() {
@Override
public void run() {
clearHistory();
}
});
clear.start();
}
})
.setNegativeButton(getResources().getString(R.string.action_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
}
}).show();
}
private void clearCookiesDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(getResources().getString(R.string.title_clear_cookies));
builder.setMessage(getResources().getString(R.string.dialog_cookies))
.setPositiveButton(getResources().getString(R.string.action_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Thread clear = new Thread(new Runnable() {
@Override
public void run() {
clearCookies();
}
});
clear.start();
}
})
.setNegativeButton(getResources().getString(R.string.action_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
}
}).show();
}
private void clearCache() {
WebView webView = new WebView(mActivity);
webView.clearCache(true);
webView.destroy();
Utils.showSnackbar(mActivity, R.string.message_cache_cleared);
}
private void clearHistory() {
WebUtils.clearHistory(getActivity());
messageHandler.sendEmptyMessage(1);
}
private void clearCookies() {
WebUtils.clearCookies(getActivity());
messageHandler.sendEmptyMessage(2);
}
private void clearWebStorage() {
WebUtils.clearWebStorage();
Utils.showSnackbar(getActivity(), R.string.message_web_storage_cleared);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// switch preferences
switch (preference.getKey()) {
case SETTINGS_LOCATION:
mPreferences.setLocationEnabled((Boolean) newValue);
cblocation.setChecked((Boolean) newValue);
return true;
case SETTINGS_THIRDPCOOKIES:
mPreferences.setBlockThirdPartyCookiesEnabled((Boolean) newValue);
cb3cookies.setChecked((Boolean) newValue);
return true;
case SETTINGS_SAVEPASSWORD:
mPreferences.setSavePasswordsEnabled((Boolean) newValue);
cbsavepasswords.setChecked((Boolean) newValue);
return true;
case SETTINGS_CACHEEXIT:
mPreferences.setClearCacheExit((Boolean) newValue);
cbcacheexit.setChecked((Boolean) newValue);
return true;
case SETTINGS_HISTORYEXIT:
mPreferences.setClearHistoryExitEnabled((Boolean) newValue);
cbhistoryexit.setChecked((Boolean) newValue);
return true;
case SETTINGS_COOKIEEXIT:
mPreferences.setClearCookiesExitEnabled((Boolean) newValue);
cbcookiesexit.setChecked((Boolean) newValue);
return true;
case SETTINGS_WEBSTORAGEEXIT:
mPreferences.setClearWebStorageExitEnabled((Boolean) newValue);
cbwebstorageexit.setChecked((Boolean) newValue);
return true;
default:
return false;
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.object;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import acr.browser.lightning.controller.BrowserController;
public class ClickHandler extends Handler {
private BrowserController mBrowserController;
public ClickHandler(Context context) {
try {
mBrowserController = (BrowserController) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement BrowserController");
}
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String url = msg.getData().getString("url");
mBrowserController.longClickPage(url);
}
}
@@ -0,0 +1,186 @@
package acr.browser.lightning.object;
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
/**
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate
* between them.
*/
public class DrawerArrowDrawable extends Drawable {
private final Paint mPaint = new Paint();
// The angle in degress that the arrow head is inclined at.
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
private final float mBarThickness;
// The length of top and bottom bars when they merge into an arrow
private final float mTopBottomArrowSize;
// The length of middle bar
private final float mBarSize;
// The length of the middle bar when arrow is shaped
private final float mMiddleArrowSize;
// The space between bars when they are parallel
private final float mBarGap;
// Whether bars should spin or not during progress
private final boolean mSpin;
// Use Path instead of canvas operations so that if color has transparency,
// overlapping sections
// wont look different
private final Path mPath = new Path();
// The reported intrinsic size of the drawable.
private final int mSize;
// Whether we should mirror animation when animation is reversed.
private boolean mVerticalMirror = false;
// The interpolated version of the original progress
private float mProgress;
/**
* @param context
* used to get the configuration for the drawable from
*/
public DrawerArrowDrawable(Context context) {
final TypedArray typedArray = context.getTheme().obtainStyledAttributes(null,
R.styleable.DrawerArrowToggle, R.attr.drawerArrowStyle,
R.style.Base_Widget_AppCompat_DrawerArrowToggle);
mPaint.setAntiAlias(true);
mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0));
mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0);
mBarSize = typedArray.getDimension(R.styleable.DrawerArrowToggle_barLength, 0);
mTopBottomArrowSize = typedArray.getDimension(
R.styleable.DrawerArrowToggle_arrowHeadLength, 0);
mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0);
mBarGap = typedArray.getDimension(R.styleable.DrawerArrowToggle_gapBetweenBars, 0);
mSpin = typedArray.getBoolean(R.styleable.DrawerArrowToggle_spinBars, true);
mMiddleArrowSize = typedArray.getDimension(
R.styleable.DrawerArrowToggle_arrowShaftLength, 0);
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStrokeWidth(mBarThickness);
}
protected boolean isLayoutRtl() {
return false;
}
/**
* If set, canvas is flipped when progress reached to end and going back to
* start.
*/
protected void setVerticalMirror(boolean verticalMirror) {
mVerticalMirror = verticalMirror;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
final boolean isRtl = isLayoutRtl();
// Interpolated widths of arrow bars
final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress);
final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress);
// Interpolated size of middle bar
final float middleBarCut = lerp(0, mBarThickness / 2, mProgress);
// The rotation of the top and bottom bars (that make the arrow head)
final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress);
// The whole canvas rotates as the transition happens
final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress);
final float topBottomBarOffset = lerp(mBarGap + mBarThickness, 0, mProgress);
mPath.rewind();
final float arrowEdge = -middleBarSize / 2;
// draw middle bar
mPath.moveTo(arrowEdge + middleBarCut, 0);
mPath.rLineTo(middleBarSize - middleBarCut, 0);
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
// top bar
mPath.moveTo(arrowEdge, topBottomBarOffset);
mPath.rLineTo(arrowWidth, arrowHeight);
// bottom bar
mPath.moveTo(arrowEdge, -topBottomBarOffset);
mPath.rLineTo(arrowWidth, -arrowHeight);
mPath.moveTo(0, 0);
mPath.close();
canvas.save();
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
// the arrow pointing the other way for RTL.
if (mSpin) {
canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1), bounds.centerX(),
bounds.centerY());
} else if (isRtl) {
canvas.rotate(180, bounds.centerX(), bounds.centerY());
}
canvas.translate(bounds.centerX(), bounds.centerY());
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
// override
public boolean isAutoMirrored() {
// Draws rotated 180 degrees in RTL mode.
return true;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getIntrinsicHeight() {
return mSize;
}
@Override
public int getIntrinsicWidth() {
return mSize;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public float getProgress() {
return mProgress;
}
public void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
/**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
}
@@ -0,0 +1,468 @@
package acr.browser.lightning.object;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import acr.browser.lightning.R;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryDatabase;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.Utils;
public class SearchAdapter extends BaseAdapter implements Filterable {
private final List<HistoryItem> mHistory = new ArrayList<>();
private final List<HistoryItem> mBookmarks = new ArrayList<>();
private final List<HistoryItem> mSuggestions = new ArrayList<>();
private final List<HistoryItem> mFilteredList = new ArrayList<>();
private final List<HistoryItem> mAllBookmarks = new ArrayList<>();
private final Object mLock = new Object();
private HistoryDatabase mDatabaseHandler;
private final Context mContext;
private boolean mUseGoogle = true;
private boolean mIsExecuting = false;
private final boolean mDarkTheme;
private final boolean mIncognito;
private final BookmarkManager mBookmarkManager;
private static final String CACHE_FILE_TYPE = ".sgg";
private static final String ENCODING = "ISO-8859-1";
private static final long INTERVAL_DAY = 86400000;
private static final int MAX_SUGGESTIONS = 5;
private final SuggestionsComparator mComparator = new SuggestionsComparator();
private final String mSearchSubtitle;
private SearchFilter mFilter;
private final Drawable mSearchDrawable;
private final Drawable mHistoryDrawable;
private final Drawable mBookmarkDrawable;
public SearchAdapter(Context context, boolean dark, boolean incognito) {
mDatabaseHandler = HistoryDatabase.getInstance(context.getApplicationContext());
mBookmarkManager = BookmarkManager.getInstance(context.getApplicationContext());
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled();
mContext = context;
mSearchSubtitle = mContext.getString(R.string.suggestion);
mDarkTheme = dark || incognito;
mIncognito = incognito;
Thread delete = new Thread(new Runnable() {
@Override
public void run() {
deleteOldCacheFiles(mContext);
}
});
mSearchDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_search, mDarkTheme);
mBookmarkDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_bookmark, mDarkTheme);
mHistoryDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_history, mDarkTheme);
delete.setPriority(Thread.MIN_PRIORITY);
delete.start();
}
private void deleteOldCacheFiles(Context context) {
File dir = new File(context.getCacheDir().toString());
String[] fileList = dir.list(new NameFilter());
long earliestTimeAllowed = System.currentTimeMillis() - INTERVAL_DAY;
for (String fileName : fileList) {
File file = new File(dir.getPath() + fileName);
if (earliestTimeAllowed > file.lastModified()) {
file.delete();
}
}
}
private class NameFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith(CACHE_FILE_TYPE);
}
}
public void refreshPreferences() {
mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled();
if (!mUseGoogle) {
synchronized (mSuggestions) {
mSuggestions.clear();
}
}
mDatabaseHandler = HistoryDatabase.getInstance(mContext.getApplicationContext());
}
public void refreshBookmarks() {
synchronized (mLock) {
mAllBookmarks.clear();
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
}
}
@Override
public int getCount() {
return mFilteredList.size();
}
@Override
public Object getItem(int position) {
return mFilteredList.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SuggestionHolder holder;
if (convertView == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
convertView = inflater.inflate(R.layout.two_line_autocomplete, parent, false);
holder = new SuggestionHolder();
holder.mTitle = (TextView) convertView.findViewById(R.id.title);
holder.mUrl = (TextView) convertView.findViewById(R.id.url);
holder.mImage = (ImageView) convertView.findViewById(R.id.suggestionIcon);
convertView.setTag(holder);
} else {
holder = (SuggestionHolder) convertView.getTag();
}
HistoryItem web;
web = mFilteredList.get(position);
holder.mTitle.setText(web.getTitle());
holder.mUrl.setText(web.getUrl());
Drawable image;
switch (web.getImageId()) {
case R.drawable.ic_bookmark: {
if (mDarkTheme)
holder.mTitle.setTextColor(Color.WHITE);
image = mBookmarkDrawable;
break;
}
case R.drawable.ic_search: {
if (mDarkTheme)
holder.mTitle.setTextColor(Color.WHITE);
image = mSearchDrawable;
break;
}
case R.drawable.ic_history: {
if (mDarkTheme)
holder.mTitle.setTextColor(Color.WHITE);
image = mHistoryDrawable;
break;
}
default:
if (mDarkTheme)
holder.mTitle.setTextColor(Color.WHITE);
image = mSearchDrawable;
break;
}
holder.mImage.setImageDrawable(image);
return convertView;
}
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new SearchFilter();
}
return mFilter;
}
private class SearchFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint == null) {
return results;
}
String query = constraint.toString().toLowerCase(Locale.getDefault());
if (mUseGoogle && !mIncognito && !mIsExecuting) {
new RetrieveSearchSuggestions().execute(query);
}
int counter = 0;
synchronized (mBookmarks) {
mBookmarks.clear();
synchronized (mLock) {
for (int n = 0; n < mAllBookmarks.size(); n++) {
if (counter >= 5) {
break;
}
if (mAllBookmarks.get(n).getTitle().toLowerCase(Locale.getDefault())
.startsWith(query)) {
mBookmarks.add(mAllBookmarks.get(n));
counter++;
} else if (mAllBookmarks.get(n).getUrl().contains(query)) {
mBookmarks.add(mAllBookmarks.get(n));
counter++;
}
}
}
}
if (mDatabaseHandler == null || mDatabaseHandler.isClosed()) {
mDatabaseHandler = HistoryDatabase.getInstance(mContext.getApplicationContext());
}
List<HistoryItem> historyList = mDatabaseHandler.findItemsContaining(constraint.toString());
synchronized (mHistory) {
mHistory.clear();
mHistory.addAll(historyList);
}
results.count = 1;
return results;
}
@Override
public CharSequence convertResultToString(Object resultValue) {
return ((HistoryItem) resultValue).getUrl();
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
synchronized (mFilteredList) {
mFilteredList.clear();
List<HistoryItem> filtered = getFilteredList();
Collections.sort(filtered, mComparator);
mFilteredList.addAll(filtered);
}
notifyDataSetChanged();
}
}
private class SuggestionHolder {
ImageView mImage;
TextView mTitle;
TextView mUrl;
}
private class RetrieveSearchSuggestions extends AsyncTask<String, Void, List<HistoryItem>> {
private XmlPullParserFactory mFactory;
private XmlPullParser mXpp;
@Override
protected List<HistoryItem> doInBackground(String... arg0) {
mIsExecuting = true;
List<HistoryItem> filter = new ArrayList<>();
String query = arg0[0];
try {
query = query.replace(" ", "+");
URLEncoder.encode(query, ENCODING);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
File cache = downloadSuggestionsForQuery(query);
if (!cache.exists()) {
return filter;
}
InputStream fileInput = null;
try {
fileInput = new BufferedInputStream(new FileInputStream(cache));
if (mFactory == null) {
mFactory = XmlPullParserFactory.newInstance();
mFactory.setNamespaceAware(true);
}
if (mXpp == null) {
mXpp = mFactory.newPullParser();
}
mXpp.setInput(fileInput, ENCODING);
int eventType = mXpp.getEventType();
int counter = 0;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && "suggestion".equals(mXpp.getName())) {
String suggestion = mXpp.getAttributeValue(null, "data");
filter.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"',
suggestion, R.drawable.ic_search));
counter++;
if (counter >= 5) {
break;
}
}
eventType = mXpp.next();
}
} catch (Exception e) {
return filter;
} finally {
Utils.close(fileInput);
}
return filter;
}
@Override
protected void onPostExecute(List<HistoryItem> result) {
synchronized (mSuggestions) {
mSuggestions.clear();
mSuggestions.addAll(result);
}
synchronized (mFilteredList) {
mFilteredList.clear();
List<HistoryItem> filtered = getFilteredList();
Collections.sort(filtered, mComparator);
mFilteredList.addAll(filtered);
notifyDataSetChanged();
}
mIsExecuting = false;
}
}
private File downloadSuggestionsForQuery(String query) {
File cacheFile = new File(mContext.getCacheDir(), query.hashCode() + CACHE_FILE_TYPE);
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) {
return cacheFile;
}
if (!isNetworkConnected(mContext)) {
return cacheFile;
}
InputStream in = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://google.com/complete/search?q=" + query
+ "&output=toolbar&hl=en");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
in = connection.getInputStream();
if (in != null) {
fos = new FileOutputStream(cacheFile);
int buffer;
while ((buffer = in.read()) != -1) {
fos.write(buffer);
}
fos.flush();
}
cacheFile.setLastModified(System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
} finally {
Utils.close(in);
Utils.close(fos);
}
return cacheFile;
}
private static boolean isNetworkConnected(Context context) {
NetworkInfo networkInfo = getActiveNetworkInfo(context);
return networkInfo != null && networkInfo.isConnected();
}
private static NetworkInfo getActiveNetworkInfo(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
return null;
}
return connectivity.getActiveNetworkInfo();
}
//TODO Write simpler algorithm
// private List<HistoryItem> getSuggestions() {
// List<HistoryItem> filteredList = new ArrayList<>();
//
// int suggestionsSize = mSuggestions.size();
// int historySize = mHistory.size();
// int bookmarkSize = mBookmarks.size();
//
// int maxSuggestions = (bookmarkSize + historySize < 3) ? (5 - bookmarkSize - historySize) : (bookmarkSize < 2) ? (4 - bookmarkSize) : (historySize < 1) ? 3 : 2;
// int maxHistory = (suggestionsSize + bookmarkSize < 4) ? (5 - suggestionsSize - bookmarkSize) : 1;
// int maxBookmarks = (suggestionsSize + historySize < 3) ? (5 - suggestionsSize - historySize) : 2;
//
// for (int n = 0; n < bookmarkSize && n < maxBookmarks; n++) {
// filteredList.add(mBookmarks.get(n));
// }
//
// for (int n = 0; n < historySize && n < maxHistory; n++) {
// filteredList.add(mHistory.get(n));
// }
//
// for (int n = 0; n < suggestionsSize && n < maxSuggestions; n++) {
// filteredList.add(mSuggestions.get(n));
// }
// return filteredList;
// }
private List<HistoryItem> getFilteredList() {
List<HistoryItem> list = new ArrayList<>();
synchronized (mBookmarks) {
synchronized (mHistory) {
synchronized (mSuggestions) {
Iterator<HistoryItem> bookmark = mBookmarks.iterator();
Iterator<HistoryItem> history = mHistory.iterator();
Iterator<HistoryItem> suggestion = mSuggestions.listIterator();
while (list.size() < MAX_SUGGESTIONS) {
if (!bookmark.hasNext() && !suggestion.hasNext() && !history.hasNext()) {
return list;
}
if (bookmark.hasNext()) {
list.add(bookmark.next());
}
if (suggestion.hasNext() && list.size() < MAX_SUGGESTIONS) {
list.add(suggestion.next());
}
if (history.hasNext() && list.size() < MAX_SUGGESTIONS) {
list.add(history.next());
}
}
}
}
}
return list;
}
private class SuggestionsComparator implements Comparator<HistoryItem> {
@Override
public int compare(HistoryItem lhs, HistoryItem rhs) {
if (lhs.getImageId() == rhs.getImageId()) return 0;
if (lhs.getImageId() == R.drawable.ic_bookmark) return -1;
if (rhs.getImageId() == R.drawable.ic_bookmark) return 1;
if (lhs.getImageId() == R.drawable.ic_history) return -1;
return 1;
}
}
}
@@ -0,0 +1,454 @@
package acr.browser.lightning.preference;
import android.content.SharedPreferences;
import android.os.Environment;
import acr.browser.lightning.activity.BrowserApp;
import acr.browser.lightning.constant.Constants;
public class PreferenceManager {
private static class Name {
public static final String ADOBE_FLASH_SUPPORT = "enableflash";
public static final String BLOCK_ADS = "AdBlock";
public static final String BLOCK_IMAGES = "blockimages";
public static final String CLEAR_CACHE_EXIT = "cache";
public static final String COOKIES = "cookies";
public static final String DOWNLOAD_DIRECTORY = "download";
public static final String FULL_SCREEN = "fullscreen";
public static final String HIDE_STATUS_BAR = "hidestatus";
public static final String HOMEPAGE = "home";
public static final String INCOGNITO_COOKIES = "incognitocookies";
public static final String JAVASCRIPT = "java";
public static final String LOCATION = "location";
public static final String OVERVIEW_MODE = "overviewmode";
public static final String POPUPS = "newwindows";
public static final String RESTORE_LOST_TABS = "restoreclosed";
public static final String SAVE_PASSWORDS = "passwords";
public static final String SEARCH = "search";
public static final String SEARCH_URL = "searchurl";
public static final String TEXT_REFLOW = "textreflow";
public static final String TEXT_SIZE = "textsize";
public static final String URL_MEMORY = "memory";
public static final String USE_WIDE_VIEWPORT = "wideviewport";
public static final String USER_AGENT = "agentchoose";
public static final String USER_AGENT_STRING = "userAgentString";
public static final String GOOGLE_SEARCH_SUGGESTIONS = "GoogleSearchSuggestions";
public static final String CLEAR_HISTORY_EXIT = "clearHistoryExit";
public static final String CLEAR_COOKIES_EXIT = "clearCookiesExit";
public static final String SAVE_URL = "saveUrl";
public static final String RENDERING_MODE = "renderMode";
public static final String BLOCK_THIRD_PARTY = "thirdParty";
public static final String ENABLE_COLOR_MODE = "colorMode";
public static final String URL_BOX_CONTENTS = "urlContent";
public static final String INVERT_COLORS = "invertColors";
public static final String READING_TEXT_SIZE = "readingTextSize";
public static final String THEME = "Theme";
public static final String DEFAULT_BOOKMARKS = "defaultBookmarks";
public static final String TEXT_ENCODING = "textEncoding";
public static final String CLEAR_WEBSTORAGE_EXIT = "clearWebStorageExit";
public static final String SHOW_TABS_IN_DRAWER = "showTabsInDrawer";
public static final String USE_PROXY = "useProxy";
public static final String PROXY_CHOICE = "proxyChoice";
public static final String USE_PROXY_HOST = "useProxyHost";
public static final String USE_PROXY_PORT = "useProxyPort";
public static final String INITIAL_CHECK_FOR_TOR = "checkForTor";
public static final String INITIAL_CHECK_FOR_I2P = "checkForI2P";
}
private static PreferenceManager mInstance;
private SharedPreferences mPrefs;
private static final String PREFERENCES = "settings";
public static PreferenceManager getInstance() {
if (mInstance == null) {
mInstance = new PreferenceManager();
}
return mInstance;
}
private PreferenceManager() {
mPrefs = BrowserApp.getAppContext().getSharedPreferences(PREFERENCES, 0);
}
public boolean getAdBlockEnabled() {
return mPrefs.getBoolean(Name.BLOCK_ADS, false);
}
public boolean getBlockImagesEnabled() {
return mPrefs.getBoolean(Name.BLOCK_IMAGES, false);
}
public boolean getBlockThirdPartyCookiesEnabled() {
return mPrefs.getBoolean(Name.BLOCK_THIRD_PARTY, false);
}
public boolean getCheckedForTor() {
return mPrefs.getBoolean(Name.INITIAL_CHECK_FOR_TOR, false);
}
public boolean getCheckedForI2P() {
return mPrefs.getBoolean(Name.INITIAL_CHECK_FOR_I2P, false);
}
public boolean getClearCacheExit() {
return mPrefs.getBoolean(Name.CLEAR_CACHE_EXIT, false);
}
public boolean getClearCookiesExitEnabled() {
return mPrefs.getBoolean(Name.CLEAR_COOKIES_EXIT, false);
}
public boolean getClearWebStorageExitEnabled() {
return mPrefs.getBoolean(Name.CLEAR_WEBSTORAGE_EXIT, false);
}
public boolean getClearHistoryExitEnabled() {
return mPrefs.getBoolean(Name.CLEAR_HISTORY_EXIT, false);
}
public boolean getColorModeEnabled() {
return mPrefs.getBoolean(Name.ENABLE_COLOR_MODE, false);
}
public boolean getCookiesEnabled() {
return mPrefs.getBoolean(Name.COOKIES, true);
}
public boolean getDefaultBookmarks() {
return mPrefs.getBoolean(Name.DEFAULT_BOOKMARKS, true);
}
public String getDownloadDirectory() {
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, Environment.DIRECTORY_DOWNLOADS);
}
public int getFlashSupport() {
return mPrefs.getInt(Name.ADOBE_FLASH_SUPPORT, 0);
}
public boolean getFullScreenEnabled() {
return mPrefs.getBoolean(Name.FULL_SCREEN, false);
}
public boolean getGoogleSearchSuggestionsEnabled() {
return mPrefs.getBoolean(Name.GOOGLE_SEARCH_SUGGESTIONS, true);
}
public boolean getHideStatusBarEnabled() {
return mPrefs.getBoolean(Name.HIDE_STATUS_BAR, false);
}
public String getHomepage() {
return mPrefs.getString(Name.HOMEPAGE, Constants.HOMEPAGE);
}
public boolean getIncognitoCookiesEnabled() {
return mPrefs.getBoolean(Name.INCOGNITO_COOKIES, false);
}
public boolean getInvertColors() {
return mPrefs.getBoolean(Name.INVERT_COLORS, false);
}
public boolean getJavaScriptEnabled() {
return mPrefs.getBoolean(Name.JAVASCRIPT, true);
}
public boolean getLocationEnabled() {
return mPrefs.getBoolean(Name.LOCATION, false);
}
public String getMemoryUrl() {
return mPrefs.getString(Name.URL_MEMORY, "");
}
public boolean getOverviewModeEnabled() {
return mPrefs.getBoolean(Name.OVERVIEW_MODE, true);
}
public boolean getPopupsEnabled() {
return mPrefs.getBoolean(Name.POPUPS, true);
}
public String getProxyHost() {
return mPrefs.getString(Name.USE_PROXY_HOST, "localhost");
}
public int getProxyPort() {
return mPrefs.getInt(Name.USE_PROXY_PORT, 8118);
}
public int getReadingTextSize() {
return mPrefs.getInt(Name.READING_TEXT_SIZE, 2);
}
public int getRenderingMode() {
return mPrefs.getInt(Name.RENDERING_MODE, 0);
}
public boolean getRestoreLostTabsEnabled() {
return mPrefs.getBoolean(Name.RESTORE_LOST_TABS, true);
}
public String getSavedUrl() {
return mPrefs.getString(Name.SAVE_URL, null);
}
public boolean getSavePasswordsEnabled() {
return mPrefs.getBoolean(Name.SAVE_PASSWORDS, true);
}
public int getSearchChoice() {
return mPrefs.getInt(Name.SEARCH, 1);
}
public String getSearchUrl() {
return mPrefs.getString(Name.SEARCH_URL, Constants.GOOGLE_SEARCH);
}
public boolean getTextReflowEnabled() {
return mPrefs.getBoolean(Name.TEXT_REFLOW, false);
}
public int getTextSize() {
return mPrefs.getInt(Name.TEXT_SIZE, 3);
}
public int getUrlBoxContentChoice() {
return mPrefs.getInt(Name.URL_BOX_CONTENTS, 0);
}
public int getUseTheme() {
return mPrefs.getInt(Name.THEME, 0);
}
public boolean getUseProxy() {
return mPrefs.getBoolean(Name.USE_PROXY, false);
}
public int getProxyChoice() {
return mPrefs.getInt(Name.PROXY_CHOICE, Constants.NO_PROXY);
}
public int getUserAgentChoice() {
return mPrefs.getInt(Name.USER_AGENT, 1);
}
public String getUserAgentString(String def) {
return mPrefs.getString(Name.USER_AGENT_STRING, def);
}
public boolean getUseWideViewportEnabled() {
return mPrefs.getBoolean(Name.USE_WIDE_VIEWPORT, true);
}
public String getTextEncoding() {
return mPrefs.getString(Name.TEXT_ENCODING, Constants.DEFAULT_ENCODING);
}
public boolean getShowTabsInDrawer(boolean defaultValue){
return mPrefs.getBoolean(Name.SHOW_TABS_IN_DRAWER, defaultValue);
}
private void putBoolean(String name, boolean value) {
mPrefs.edit().putBoolean(name, value).apply();
}
private void putInt(String name, int value) {
mPrefs.edit().putInt(name, value).apply();
}
private void putString(String name, String value) {
mPrefs.edit().putString(name, value).apply();
}
public void setShowTabsInDrawer(boolean show){
putBoolean(Name.SHOW_TABS_IN_DRAWER, show);
}
public void setTextEncoding(String encoding) {
putString(Name.TEXT_ENCODING, encoding);
}
public void setAdBlockEnabled(boolean enable) {
putBoolean(Name.BLOCK_ADS, enable);
}
public void setBlockImagesEnabled(boolean enable) {
putBoolean(Name.BLOCK_IMAGES, enable);
}
public void setBlockThirdPartyCookiesEnabled(boolean enable) {
putBoolean(Name.BLOCK_THIRD_PARTY, enable);
}
public void setCheckedForTor(boolean check) {
putBoolean(Name.INITIAL_CHECK_FOR_TOR, check);
}
public void setCheckedForI2P(boolean check) {
putBoolean(Name.INITIAL_CHECK_FOR_I2P, check);
}
public void setClearCacheExit(boolean enable) {
putBoolean(Name.CLEAR_CACHE_EXIT, enable);
}
public void setClearCookiesExitEnabled(boolean enable) {
putBoolean(Name.CLEAR_COOKIES_EXIT, enable);
}
public void setClearWebStorageExitEnabled(boolean enable) {
putBoolean(Name.CLEAR_WEBSTORAGE_EXIT, enable);
}
public void setClearHistoryExitEnabled(boolean enable) {
putBoolean(Name.CLEAR_HISTORY_EXIT, enable);
}
public void setColorModeEnabled(boolean enable) {
putBoolean(Name.ENABLE_COLOR_MODE, enable);
}
public void setCookiesEnabled(boolean enable) {
putBoolean(Name.COOKIES, enable);
}
public void setDefaultBookmarks(boolean show) {
putBoolean(Name.DEFAULT_BOOKMARKS, show);
}
public void setDownloadDirectory(String directory) {
putString(Name.DOWNLOAD_DIRECTORY, directory);
}
public void setFlashSupport(int n) {
putInt(Name.ADOBE_FLASH_SUPPORT, n);
}
public void setFullScreenEnabled(boolean enable) {
putBoolean(Name.FULL_SCREEN, enable);
}
public void setGoogleSearchSuggestionsEnabled(boolean enabled) {
putBoolean(Name.GOOGLE_SEARCH_SUGGESTIONS, enabled);
}
public void setHideStatusBarEnabled(boolean enable) {
putBoolean(Name.HIDE_STATUS_BAR, enable);
}
public void setHomepage(String homepage) {
putString(Name.HOMEPAGE, homepage);
}
public void setIncognitoCookiesEnabled(boolean enable) {
putBoolean(Name.INCOGNITO_COOKIES, enable);
}
public void setInvertColors(boolean enable) {
putBoolean(Name.INVERT_COLORS, enable);
}
public void setJavaScriptEnabled(boolean enable) {
putBoolean(Name.JAVASCRIPT, enable);
}
public void setLocationEnabled(boolean enable) {
putBoolean(Name.LOCATION, enable);
}
public void setMemoryUrl(String url) {
putString(Name.URL_MEMORY, url);
}
public void setOverviewModeEnabled(boolean enable) {
putBoolean(Name.OVERVIEW_MODE, enable);
}
public void setPopupsEnabled(boolean enable) {
putBoolean(Name.POPUPS, enable);
}
public void setReadingTextSize(int size) {
putInt(Name.READING_TEXT_SIZE, size);
}
public void setRenderingMode(int mode) {
putInt(Name.RENDERING_MODE, mode);
}
public void setRestoreLostTabsEnabled(boolean enable) {
putBoolean(Name.RESTORE_LOST_TABS, enable);
}
public void setSavedUrl(String url) {
putString(Name.SAVE_URL, url);
}
public void setSavePasswordsEnabled(boolean enable) {
putBoolean(Name.SAVE_PASSWORDS, enable);
}
public void setSearchChoice(int choice) {
putInt(Name.SEARCH, choice);
}
public void setSearchUrl(String url) {
putString(Name.SEARCH_URL, url);
}
public void setTextReflowEnabled(boolean enable) {
putBoolean(Name.TEXT_REFLOW, enable);
}
public void setTextSize(int size) {
putInt(Name.TEXT_SIZE, size);
}
public void setUrlBoxContentChoice(int choice) {
putInt(Name.URL_BOX_CONTENTS, choice);
}
public void setUseTheme(int theme) {
putInt(Name.THEME, theme);
}
/**
* Valid choices:
* <ul>
* <li>{@link Constants#NO_PROXY}</li>
* <li>{@link Constants#PROXY_ORBOT}</li>
* <li>{@link Constants#PROXY_I2P}</li>
* </ul>
*
* @param choice the proxy to use.
*/
public void setProxyChoice(int choice) {
putBoolean(Name.USE_PROXY, choice != Constants.NO_PROXY);
putInt(Name.PROXY_CHOICE, choice);
}
public void setProxyHost(String proxyHost) {
putString(Name.USE_PROXY_HOST, proxyHost);
}
public void setProxyPort(int proxyPort) {
putInt(Name.USE_PROXY_PORT, proxyPort);
}
public void setUserAgentChoice(int choice) {
putInt(Name.USER_AGENT, choice);
}
public void setUserAgentString(String agent) {
putString(Name.USER_AGENT_STRING, agent);
}
public void setUseWideViewportEnabled(boolean enable) {
putBoolean(Name.USE_WIDE_VIEWPORT, enable);
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,246 @@
/*
* Copyright 2011 Peter Karich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.reading;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Locale;
import acr.browser.lightning.constant.Constants;
/**
* This class is not thread safe. Use one new instance every time due to
* encoding variable.
*
* @author Peter Karich
*/
public class Converter {
public final static String UTF8 = "UTF-8";
public final static String ISO = "ISO-8859-1";
public final static int K2 = 2048;
private int maxBytes = 1000000 / 2;
private String encoding;
private String url;
public Converter(String urlOnlyHint) {
url = urlOnlyHint;
}
public Converter() {
}
public Converter setMaxBytes(int maxBytes) {
this.maxBytes = maxBytes;
return this;
}
public static String extractEncoding(String contentType) {
String[] values;
if (contentType != null)
values = contentType.split(";");
else
values = new String[0];
String charset = "";
for (String value : values) {
value = value.trim().toLowerCase(Locale.getDefault());
if (value.startsWith("charset="))
charset = value.substring("charset=".length());
}
// http1.1 says ISO-8859-1 is the default charset
if (charset.isEmpty())
charset = ISO;
return charset;
}
public String getEncoding() {
if (encoding == null)
return "";
return encoding.toLowerCase(Locale.getDefault());
}
public String streamToString(InputStream is) {
return streamToString(is, maxBytes, encoding);
}
public String streamToString(InputStream is, String enc) {
return streamToString(is, maxBytes, enc);
}
/**
* reads bytes off the string and returns a string
*
* @param is input stream to read
* @param maxBytes
* The max bytes that we want to read from the input stream
* @return String
*/
public String streamToString(InputStream is, int maxBytes, String enc) {
encoding = enc;
// Http 1.1. standard is iso-8859-1 not utf8 :(
// but we force utf-8 as youtube assumes it ;)
if (encoding == null || encoding.isEmpty())
encoding = UTF8;
BufferedInputStream in = null;
try {
in = new BufferedInputStream(is, K2);
ByteArrayOutputStream output = new ByteArrayOutputStream();
// detect encoding with the help of meta tag
try {
in.mark(K2 * 2);
String tmpEnc = detectCharset("charset=", output, in, encoding);
if (tmpEnc != null)
encoding = tmpEnc;
else {
Log.d(Constants.TAG, "no charset found in first stage");
// detect with the help of xml beginning ala
// encoding="charset"
tmpEnc = detectCharset("encoding=", output, in, encoding);
if (tmpEnc != null)
encoding = tmpEnc;
else
Log.d(Constants.TAG, "no charset found in second stage");
}
if (!Charset.isSupported(encoding))
throw new UnsupportedEncodingException(encoding);
} catch (UnsupportedEncodingException e) {
Log.d(Constants.TAG,
"Using default encoding:" + UTF8 + " problem:" + e.getMessage()
+ " encoding:" + encoding + ' ' + url);
encoding = UTF8;
}
// SocketException: Connection reset
// IOException: missing CR => problem on server (probably some xml
// character thing?)
// IOException: Premature EOF => socket unexpectly closed from
// server
int bytesRead = output.size();
byte[] arr = new byte[K2];
while (true) {
if (bytesRead >= maxBytes) {
Log.d(Constants.TAG, "Maxbyte of " + maxBytes
+ " exceeded! Maybe html is now broken but try it nevertheless. Url: "
+ url);
break;
}
int n = in.read(arr);
if (n < 0)
break;
bytesRead += n;
output.write(arr, 0, n);
}
return output.toString(encoding);
} catch (IOException e) {
Log.e(Constants.TAG, e.toString() + " url:" + url);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return "";
}
/**
* This method detects the charset even if the first call only returns some
* bytes. It will read until 4K bytes are reached and then try to determine
* the encoding
*
* @throws IOException
*/
protected static String detectCharset(String key, ByteArrayOutputStream bos, BufferedInputStream in,
String enc) throws IOException {
// Grab better encoding from stream
byte[] arr = new byte[K2];
int nSum = 0;
while (nSum < K2) {
int n = in.read(arr);
if (n < 0)
break;
nSum += n;
bos.write(arr, 0, n);
}
String str = bos.toString(enc);
int encIndex = str.indexOf(key);
int clength = key.length();
if (encIndex > 0) {
char startChar = str.charAt(encIndex + clength);
int lastEncIndex;
if (startChar == '\'')
// if we have charset='something'
lastEncIndex = str.indexOf("'", ++encIndex + clength);
else if (startChar == '\"')
// if we have charset="something"
lastEncIndex = str.indexOf("\"", ++encIndex + clength);
else {
// if we have "text/html; charset=utf-8"
int first = str.indexOf("\"", encIndex + clength);
if (first < 0)
first = Integer.MAX_VALUE;
// or "text/html; charset=utf-8 "
int sec = str.indexOf(" ", encIndex + clength);
if (sec < 0)
sec = Integer.MAX_VALUE;
lastEncIndex = Math.min(first, sec);
// or "text/html; charset=utf-8 '
int third = str.indexOf("'", encIndex + clength);
if (third > 0)
lastEncIndex = Math.min(lastEncIndex, third);
}
// re-read byte array with different encoding
// assume that the encoding string cannot be greater than 40 chars
if (lastEncIndex > encIndex + clength && lastEncIndex < encIndex + clength + 40) {
String tmpEnc = SHelper.encodingCleanup(str.substring(encIndex + clength,
lastEncIndex));
try {
in.reset();
bos.reset();
return tmpEnc;
} catch (IOException ex) {
Log.e(Constants.TAG, "Couldn't reset stream to re-read with new encoding "
+ tmpEnc + ' ' + ex.toString());
}
}
}
return null;
}
}
@@ -0,0 +1,469 @@
/*
* Copyright 2011 Peter Karich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.reading;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* Class to fetch articles. This class is thread safe.
*
* @author Peter Karich
*/
public class HtmlFetcher {
static {
SHelper.enableCookieMgmt();
SHelper.enableUserAgentOverwrite();
SHelper.enableAnySSL();
}
public static void main(String[] args) throws Exception {
BufferedReader reader = new BufferedReader(new FileReader("urls.txt"));
String line;
Set<String> existing = new LinkedHashSet<>();
while ((line = reader.readLine()) != null) {
int index1 = line.indexOf("\"");
int index2 = line.indexOf("\"", index1 + 1);
String url = line.substring(index1 + 1, index2);
String domainStr = SHelper.extractDomain(url, true);
String counterStr = "";
// TODO more similarities
if (existing.contains(domainStr))
counterStr = "2";
else
existing.add(domainStr);
String html = new HtmlFetcher().fetchAsString(url, 2000);
String outFile = domainStr + counterStr + ".html";
BufferedWriter writer = new BufferedWriter(new FileWriter(outFile));
writer.write(html);
writer.close();
}
reader.close();
}
private String referrer = "http://jetsli.de/crawler";
private String userAgent = "Mozilla/5.0 (compatible; Jetslide; +" + referrer + ')';
private String cacheControl = "max-age=0";
private String language = "en-us";
private String accept = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
private String charset = "UTF-8";
private SCache cache;
private final AtomicInteger cacheCounter = new AtomicInteger(0);
private int maxTextLength = -1;
private ArticleTextExtractor extractor = new ArticleTextExtractor();
private Set<String> furtherResolveNecessary = new LinkedHashSet<String>() {
{
add("bit.ly");
add("cli.gs");
add("deck.ly");
add("fb.me");
add("feedproxy.google.com");
add("flic.kr");
add("fur.ly");
add("goo.gl");
add("is.gd");
add("ink.co");
add("j.mp");
add("lnkd.in");
add("on.fb.me");
add("ow.ly");
add("plurl.us");
add("sns.mx");
add("snurl.com");
add("su.pr");
add("t.co");
add("tcrn.ch");
add("tl.gd");
add("tiny.cc");
add("tinyurl.com");
add("tmi.me");
add("tr.im");
add("twurl.nl");
}
};
public HtmlFetcher() {
}
public void setExtractor(ArticleTextExtractor extractor) {
this.extractor = extractor;
}
public ArticleTextExtractor getExtractor() {
return extractor;
}
public HtmlFetcher setCache(SCache cache) {
this.cache = cache;
return this;
}
public SCache getCache() {
return cache;
}
public int getCacheCounter() {
return cacheCounter.get();
}
public HtmlFetcher clearCacheCounter() {
cacheCounter.set(0);
return this;
}
public HtmlFetcher setMaxTextLength(int maxTextLength) {
this.maxTextLength = maxTextLength;
return this;
}
public int getMaxTextLength() {
return maxTextLength;
}
public void setAccept(String accept) {
this.accept = accept;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setCacheControl(String cacheControl) {
this.cacheControl = cacheControl;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getReferrer() {
return referrer;
}
public HtmlFetcher setReferrer(String referrer) {
this.referrer = referrer;
return this;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public String getAccept() {
return accept;
}
public String getCacheControl() {
return cacheControl;
}
public String getCharset() {
return charset;
}
public JResult fetchAndExtract(String url, int timeout, boolean resolve) throws Exception {
return fetchAndExtract(url, timeout, resolve, 0, false);
}
// main workhorse to call externally
public JResult fetchAndExtract(String url, int timeout, boolean resolve,
int maxContentSize, boolean forceReload) throws Exception {
String originalUrl = url;
url = SHelper.removeHashbang(url);
String gUrl = SHelper.getUrlFromUglyGoogleRedirect(url);
if (gUrl != null)
url = gUrl;
else {
gUrl = SHelper.getUrlFromUglyFacebookRedirect(url);
if (gUrl != null)
url = gUrl;
}
if (resolve) {
// check if we can avoid resolving the URL (which hits the website!)
JResult res = getFromCache(url, originalUrl);
if (res != null)
return res;
String resUrl = getResolvedUrl(url, timeout, 0);
if (resUrl.isEmpty()) {
JResult result = new JResult();
if (cache != null)
cache.put(url, result);
return result.setUrl(url);
}
// if resolved url is different then use it!
if (!resUrl.equals(url)) {
// this is necessary e.g. for some homebaken url resolvers which return
// the resolved url relative to url!
url = SHelper.useDomainOfFirstArg4Second(url, resUrl);
}
}
// check if we have the (resolved) URL in cache
JResult res = getFromCache(url, originalUrl);
if (res != null)
return res;
JResult result = new JResult();
// or should we use? <link rel="canonical" href="http://www.N24.de/news/newsitem_6797232.html"/>
result.setUrl(url);
result.setOriginalUrl(originalUrl);
// Immediately put the url into the cache as extracting content takes time.
if (cache != null) {
cache.put(originalUrl, result);
cache.put(url, result);
}
// extract content to the extent appropriate for content type
String lowerUrl = url.toLowerCase();
if (SHelper.isDoc(lowerUrl) || SHelper.isApp(lowerUrl) || SHelper.isPackage(lowerUrl)) {
// skip
} else if (SHelper.isVideo(lowerUrl) || SHelper.isAudio(lowerUrl)) {
result.setVideoUrl(url);
} else if (SHelper.isImage(lowerUrl)) {
result.setImageUrl(url);
} else {
try {
String urlToDownload = url;
if (forceReload) {
urlToDownload = getURLtoBreakCache(url);
}
extractor.extractContent(result, fetchAsString(urlToDownload, timeout), maxContentSize);
} catch (IOException io) {
// do nothing
}
if (result.getFaviconUrl().isEmpty())
result.setFaviconUrl(SHelper.getDefaultFavicon(url));
// some links are relative to root and do not include the domain of the url :(
if (!result.getFaviconUrl().isEmpty())
result.setFaviconUrl(fixUrl(url, result.getFaviconUrl()));
if (!result.getImageUrl().isEmpty())
result.setImageUrl(fixUrl(url, result.getImageUrl()));
if (!result.getVideoUrl().isEmpty())
result.setVideoUrl(fixUrl(url, result.getVideoUrl()));
if (!result.getRssUrl().isEmpty())
result.setRssUrl(fixUrl(url, result.getRssUrl()));
}
result.setText(lessText(result.getText()));
synchronized (result) {
result.notifyAll();
}
return result;
}
// Ugly hack to break free from any cached versions, a few URLs required this.
public static String getURLtoBreakCache(String url) {
try {
URL aURL = new URL(url);
if (aURL.getQuery() != null && aURL.getQuery().isEmpty()) {
return url + "?1";
} else {
return url + "&1";
}
} catch (MalformedURLException e) {
return url;
}
}
public String lessText(String text) {
if (text == null)
return "";
if (maxTextLength >= 0 && text.length() > maxTextLength)
return text.substring(0, maxTextLength);
return text;
}
private static String fixUrl(String url, String urlOrPath) {
return SHelper.useDomainOfFirstArg4Second(url, urlOrPath);
}
public String fetchAsString(String urlAsString, int timeout)
throws MalformedURLException, IOException {
return fetchAsString(urlAsString, timeout, true);
}
// main routine to get raw webpage content
public String fetchAsString(String urlAsString, int timeout, boolean includeSomeGooseOptions)
throws MalformedURLException, IOException {
HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, includeSomeGooseOptions);
hConn.setInstanceFollowRedirects(true);
String encoding = hConn.getContentEncoding();
InputStream is;
if ("gzip".equalsIgnoreCase(encoding)) {
is = new GZIPInputStream(hConn.getInputStream());
} else if ("deflate".equalsIgnoreCase(encoding)) {
is = new InflaterInputStream(hConn.getInputStream(), new Inflater(true));
} else {
is = hConn.getInputStream();
}
String enc = Converter.extractEncoding(hConn.getContentType());
return createConverter(urlAsString).streamToString(is, enc);
}
public static Converter createConverter(String url) {
return new Converter(url);
}
/**
* On some devices we have to hack:
* http://developers.sun.com/mobility/reference/techart/design_guidelines/http_redirection.html
*
* @param timeout Sets a specified timeout value, in milliseconds
* @return the resolved url if any. Or null if it couldn't resolve the url
* (within the specified time) or the same url if response code is OK
*/
public String getResolvedUrl(String urlAsString, int timeout,
int num_redirects) {
String newUrl = null;
int responseCode = -1;
try {
HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, true);
// force no follow
hConn.setInstanceFollowRedirects(false);
// the program doesn't care what the content actually is !!
// http://java.sun.com/developer/JDCTechTips/2003/tt0422.html
hConn.setRequestMethod("HEAD");
hConn.connect();
responseCode = hConn.getResponseCode();
hConn.getInputStream().close();
if (responseCode == HttpURLConnection.HTTP_OK)
return urlAsString;
newUrl = hConn.getHeaderField("Location");
// Note that the max recursion level is 5.
if (responseCode / 100 == 3 && newUrl != null && num_redirects < 5) {
newUrl = newUrl.replaceAll(" ", "+");
// some services use (none-standard) utf8 in their location header
if (urlAsString.startsWith("http://bit.ly")
|| urlAsString.startsWith("http://is.gd"))
newUrl = encodeUriFromHeader(newUrl);
// AP: This code is not longer need, instead we always follow
// multiple redirects.
//
// fix problems if shortened twice. as it is often the case after twitters' t.co bullshit
//if (furtherResolveNecessary.contains(SHelper.extractDomain(newUrl, true)))
// newUrl = getResolvedUrl(newUrl, timeout);
// Add support for URLs with multiple levels of redirection,
// call getResolvedUrl until there is no more redirects or a
// max number of redirects is reached.
newUrl = SHelper.useDomainOfFirstArg4Second(urlAsString, newUrl);
newUrl = getResolvedUrl(newUrl, timeout, num_redirects + 1);
return newUrl;
} else
return urlAsString;
} catch (Exception ex) {
return "";
}
}
/**
* Takes a URI that was decoded as ISO-8859-1 and applies percent-encoding
* to non-ASCII characters. Workaround for broken origin servers that send
* UTF-8 in the Location: header.
*/
static String encodeUriFromHeader(String badLocation) {
StringBuilder sb = new StringBuilder();
for (char ch : badLocation.toCharArray()) {
if (ch < (char) 128) {
sb.append(ch);
} else {
// this is ONLY valid if the uri was decoded using ISO-8859-1
sb.append(String.format("%%%02X", (int) ch));
}
}
return sb.toString();
}
protected HttpURLConnection createUrlConnection(String urlAsStr, int timeout,
boolean includeSomeGooseOptions) throws MalformedURLException, IOException {
URL url = new URL(urlAsStr);
//using proxy may increase latency
HttpURLConnection hConn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
hConn.setRequestProperty("User-Agent", userAgent);
hConn.setRequestProperty("Accept", accept);
if (includeSomeGooseOptions) {
hConn.setRequestProperty("Accept-Language", language);
hConn.setRequestProperty("content-charset", charset);
hConn.addRequestProperty("Referer", referrer);
// avoid the cache for testing purposes only?
hConn.setRequestProperty("Cache-Control", cacheControl);
}
// suggest respond to be gzipped or deflated (which is just another compression)
// http://stackoverflow.com/q/3932117
hConn.setRequestProperty("Accept-Encoding", "gzip, deflate");
hConn.setConnectTimeout(timeout);
hConn.setReadTimeout(timeout);
return hConn;
}
private JResult getFromCache(String url, String originalUrl) {
if (cache != null) {
JResult res = cache.get(url);
if (res != null) {
// e.g. the cache returned a shortened url as original url now we want to store the
// current original url! Also it can be that the cache response to url but the JResult
// does not contain it so overwrite it:
res.setUrl(url);
res.setOriginalUrl(originalUrl);
cacheCounter.addAndGet(1);
return res;
}
}
return null;
}
}
@@ -0,0 +1,31 @@
package acr.browser.lightning.reading;
import org.jsoup.nodes.Element;
/**
* Class which encapsulates the data from an image found under an element
*
* @author Chris Alexander, chris@chris-alexander.co.uk
*/
public class ImageResult {
public final String src;
public final Integer weight;
public final String title;
public final int height;
public final int width;
public final String alt;
public final boolean noFollow;
public Element element;
public ImageResult(String src, Integer weight, String title, int height, int width, String alt,
boolean noFollow) {
this.src = src;
this.weight = weight;
this.title = title;
this.height = height;
this.width = width;
this.alt = alt;
this.noFollow = noFollow;
}
}
@@ -0,0 +1,274 @@
/*
* Copyright 2011 Peter Karich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.reading;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Parsed result from web page containing important title, text and image.
*
* @author Peter Karich
*/
public class JResult implements Serializable {
private String title;
private String url;
private String originalUrl;
private String canonicalUrl;
private String imageUrl;
private String videoUrl;
private String rssUrl;
private String text;
private String faviconUrl;
private String description;
private String authorName;
private String authorDescription;
private Date date;
private Collection<String> keywords;
private List<ImageResult> images = null;
private List<Map<String, String>> links = new ArrayList<>();
private String type;
private String sitename;
private String language;
public JResult() {
}
public String getUrl() {
if (url == null)
return "";
return url;
}
public JResult setUrl(String url) {
this.url = url;
return this;
}
public JResult setOriginalUrl(String originalUrl) {
this.originalUrl = originalUrl;
return this;
}
public String getOriginalUrl() {
return originalUrl;
}
public JResult setCanonicalUrl(String canonicalUrl) {
this.canonicalUrl = canonicalUrl;
return this;
}
public String getCanonicalUrl() {
return canonicalUrl;
}
public String getFaviconUrl() {
if (faviconUrl == null)
return "";
return faviconUrl;
}
public JResult setFaviconUrl(String faviconUrl) {
this.faviconUrl = faviconUrl;
return this;
}
public JResult setRssUrl(String rssUrl) {
this.rssUrl = rssUrl;
return this;
}
public String getRssUrl() {
if (rssUrl == null)
return "";
return rssUrl;
}
public String getDescription() {
if (description == null)
return "";
return description;
}
public JResult setDescription(String description) {
this.description = description;
return this;
}
public String getAuthorName() {
if (authorName == null)
return "";
return authorName;
}
public JResult setAuthorName(String authorName) {
this.authorName = authorName;
return this;
}
public String getAuthorDescription() {
if (authorDescription == null)
return "";
return authorDescription;
}
public JResult setAuthorDescription(String authorDescription) {
this.authorDescription = authorDescription;
return this;
}
public String getImageUrl() {
if (imageUrl == null)
return "";
return imageUrl;
}
public JResult setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
return this;
}
public String getText() {
if (text == null)
return "";
return text;
}
public JResult setText(String text) {
this.text = text;
return this;
}
public String getTitle() {
if (title == null)
return "";
return title;
}
public JResult setTitle(String title) {
this.title = title;
return this;
}
public String getVideoUrl() {
if (videoUrl == null)
return "";
return videoUrl;
}
public JResult setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
return this;
}
public JResult setDate(Date date) {
this.date = date;
return this;
}
public Collection<String> getKeywords() {
return keywords;
}
public void setKeywords(Collection<String> keywords) {
this.keywords = keywords;
}
/**
* @return get date from url or guessed from text
*/
public Date getDate() {
return date;
}
/**
* @return images list
*/
public List<ImageResult> getImages() {
if (images == null)
return Collections.emptyList();
return images;
}
/**
* @return images count
*/
public int getImagesCount() {
if (images == null)
return 0;
return images.size();
}
/**
* set images list
*/
public void setImages(List<ImageResult> images) {
this.images = images;
}
public void addLink(String url, String text, Integer pos) {
Map link = new HashMap();
link.put("url", url);
link.put("text", text);
link.put("offset", String.valueOf(pos));
links.add(link);
}
public List<Map<String, String>> getLinks() {
if (links == null)
return Collections.emptyList();
return links;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSitename() {
return sitename;
}
public void setSitename(String sitename) {
this.sitename = sitename;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
@Override
public String toString() {
return "title:" + getTitle() + " imageUrl:" + getImageUrl() + " text:" + text;
}
}
@@ -0,0 +1,79 @@
/**
* Copyright (C) 2010 Peter Karich <>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package acr.browser.lightning.reading;
import java.io.Serializable;
import java.util.Map;
/**
* Simple impl of Map.Entry. So that we can have ordered maps.
*
* @author Peter Karich, peat_hal at users dot sourceforge dot
* net
*/
public class MapEntry<K, V> implements Map.Entry<K, V>, Serializable {
private static final long serialVersionUID = 1L;
private final K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
@Override
public String toString() {
return key + ", " + value;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MapEntry<K, V> other = (MapEntry<K, V>) obj;
if (this.key != other.key && (this.key == null || !this.key.equals(other.key)))
return false;
return !(this.value != other.value && (this.value == null || !this.value
.equals(other.value)));
}
@Override
public int hashCode() {
int hash = 7;
hash = 19 * hash + (this.key != null ? this.key.hashCode() : 0);
hash = 19 * hash + (this.value != null ? this.value.hashCode() : 0);
return hash;
}
}
@@ -0,0 +1,216 @@
package acr.browser.lightning.reading;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
/**
* @author goose | jim
* @author karussell
* <p/>
* this class will be responsible for taking our top node and stripping out junk
* we don't want and getting it ready for how we want it presented to the user
*/
public class OutputFormatter {
private static final int MIN_FIRST_PARAGRAPH_TEXT = 50; // Min size of first paragraph
private static final int MIN_PARAGRAPH_TEXT = 30; // Min size of any other paragraphs
private static final List<String> NODES_TO_REPLACE = Arrays.asList("strong", "b", "i");
private Pattern unlikelyPattern = Pattern.compile("display\\:none|visibility\\:hidden");
private final int minFirstParagraphText;
private final int minParagraphText;
private final List<String> nodesToReplace;
private String nodesToKeepCssSelector = "p, ol";
public OutputFormatter() {
this(MIN_FIRST_PARAGRAPH_TEXT, MIN_PARAGRAPH_TEXT, NODES_TO_REPLACE);
}
public OutputFormatter(int minParagraphText) {
this(minParagraphText, minParagraphText, NODES_TO_REPLACE);
}
public OutputFormatter(int minFirstParagraphText, int minParagraphText) {
this(minFirstParagraphText, minParagraphText, NODES_TO_REPLACE);
}
public OutputFormatter(int minFirstParagraphText, int minParagraphText,
List<String> nodesToReplace) {
this.minFirstParagraphText = minFirstParagraphText;
this.minParagraphText = minParagraphText;
this.nodesToReplace = nodesToReplace;
}
/**
* set elements to keep in output text
*/
public void setNodesToKeepCssSelector(String nodesToKeepCssSelector) {
this.nodesToKeepCssSelector = nodesToKeepCssSelector;
}
/**
* takes an element and turns the P tags into \n\n
*/
public String getFormattedText(Element topNode) {
setParagraphIndex(topNode, nodesToKeepCssSelector);
removeNodesWithNegativeScores(topNode);
StringBuilder sb = new StringBuilder();
int countOfP = append(topNode, sb, nodesToKeepCssSelector);
String str = SHelper.innerTrim(sb.toString());
int topNodeLength = topNode.text().length();
if (topNodeLength == 0) {
topNodeLength = 1;
}
boolean lowTextRatio = ((str.length() / (topNodeLength * 1.0)) < 0.25);
if (str.length() > 100 && countOfP > 0 && !lowTextRatio)
return str;
// no subelements
if (str.isEmpty() || (!topNode.text().isEmpty()
&& str.length() <= topNode.ownText().length())
|| countOfP == 0 || lowTextRatio) {
str = topNode.text();
}
// if jsoup failed to parse the whole html now parse this smaller
// snippet again to avoid html tags disturbing our text:
return Jsoup.parse(str).text();
}
/**
* If there are elements inside our top node that have a negative gravity
* score remove them
*/
protected void removeNodesWithNegativeScores(Element topNode) {
Elements gravityItems = topNode.select("*[gravityScore]");
for (Element item : gravityItems) {
int score = getScore(item);
int paragraphIndex = getParagraphIndex(item);
if (score < 0 || item.text().length() < getMinParagraph(paragraphIndex)) {
item.remove();
}
}
}
protected int append(Element node, StringBuilder sb, String tagName) {
int countOfP = 0; // Number of P elements in the article
int paragraphWithTextIndex = 0;
// is select more costly then getElementsByTag?
MAIN:
for (Element e : node.select(tagName)) {
Element tmpEl = e;
// check all elements until 'node'
while (tmpEl != null && !tmpEl.equals(node)) {
if (unlikely(tmpEl))
continue MAIN;
tmpEl = tmpEl.parent();
}
String text = node2Text(e);
if (text.isEmpty() || text.length() < getMinParagraph(paragraphWithTextIndex)
|| text.length() > SHelper.countLetters(text) * 2) {
continue;
}
if (e.tagName().equals("p")) {
countOfP++;
}
sb.append(text);
sb.append("\n\n");
paragraphWithTextIndex += 1;
}
return countOfP;
}
protected static void setParagraphIndex(Element node, String tagName) {
int paragraphIndex = 0;
for (Element e : node.select(tagName)) {
e.attr("paragraphIndex", Integer.toString(paragraphIndex++));
}
}
protected int getMinParagraph(int paragraphIndex) {
if (paragraphIndex < 1) {
return minFirstParagraphText;
} else {
return minParagraphText;
}
}
protected static int getParagraphIndex(Element el) {
try {
return Integer.parseInt(el.attr("paragraphIndex"));
} catch (NumberFormatException ex) {
return -1;
}
}
protected static int getScore(Element el) {
try {
return Integer.parseInt(el.attr("gravityScore"));
} catch (Exception ex) {
return 0;
}
}
boolean unlikely(Node e) {
if (e.attr("class") != null && e.attr("class").toLowerCase().contains("caption"))
return true;
String style = e.attr("style");
String clazz = e.attr("class");
return unlikelyPattern.matcher(style).find() || unlikelyPattern.matcher(clazz).find();
}
void appendTextSkipHidden(Element e, StringBuilder accum, int indent) {
for (Node child : e.childNodes()) {
if (unlikely(child)) {
continue;
}
if (child instanceof TextNode) {
TextNode textNode = (TextNode) child;
String txt = textNode.text();
accum.append(txt);
} else if (child instanceof Element) {
Element element = (Element) child;
if (accum.length() > 0 && element.isBlock()
&& !lastCharIsWhitespace(accum))
accum.append(' ');
else if (element.tagName().equals("br"))
accum.append(' ');
appendTextSkipHidden(element, accum, indent + 1);
}
}
}
static boolean lastCharIsWhitespace(StringBuilder accum) {
return accum.length() != 0 && Character.isWhitespace(accum.charAt(accum.length() - 1));
}
protected String node2Text(Element el) {
StringBuilder sb = new StringBuilder(200);
appendTextSkipHidden(el, sb, 0);
return sb.toString();
}
public OutputFormatter setUnlikelyPattern(String unlikelyPattern) {
this.unlikelyPattern = Pattern.compile(unlikelyPattern);
return this;
}
public OutputFormatter appendUnlikelyPattern(String str) {
return setUnlikelyPattern(unlikelyPattern.toString() + '|' + str);
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2011 Peter Karich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.reading;
/**
*
* @author Peter Karich
*/
public interface SCache {
JResult get(String url);
void put(String url, JResult res);
int getSize();
}
@@ -0,0 +1,442 @@
/*
* Copyright 2011 Peter Karich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.reading;
import org.jsoup.nodes.Element;
import java.io.UnsupportedEncodingException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* @author Peter Karich
*/
public class SHelper {
public static final String UTF8 = "UTF-8";
private static final Pattern SPACE = Pattern.compile(" ");
public static String replaceSpaces(String url) {
if (!url.isEmpty()) {
url = url.trim();
if (url.contains(" ")) {
Matcher spaces = SPACE.matcher(url);
url = spaces.replaceAll("%20");
}
}
return url;
}
public static int count(String str, String substring) {
int c = 0;
int index1 = str.indexOf(substring);
if (index1 >= 0) {
c++;
c += count(str.substring(index1 + substring.length()), substring);
}
return c;
}
/**
* remove more than two spaces or newlines
*/
public static String innerTrim(String str) {
if (str.isEmpty())
return "";
StringBuilder sb = new StringBuilder();
boolean previousSpace = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == ' ' || (int) c == 9 || c == '\n') {
previousSpace = true;
continue;
}
if (previousSpace)
sb.append(' ');
previousSpace = false;
sb.append(c);
}
return sb.toString().trim();
}
/**
* Starts reading the encoding from the first valid character until an
* invalid encoding character occurs.
*/
public static String encodingCleanup(String str) {
StringBuilder sb = new StringBuilder();
boolean startedWithCorrectString = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isDigit(c) || Character.isLetter(c) || c == '-' || c == '_') {
startedWithCorrectString = true;
sb.append(c);
continue;
}
if (startedWithCorrectString)
break;
}
return sb.toString().trim();
}
/**
* @return the longest substring as str1.substring(result[0], result[1]);
*/
public static String getLongestSubstring(String str1, String str2) {
int res[] = longestSubstring(str1, str2);
if (res == null || res[0] >= res[1])
return "";
return str1.substring(res[0], res[1]);
}
public static int[] longestSubstring(String str1, String str2) {
if (str1 == null || str1.isEmpty() || str2 == null || str2.isEmpty())
return null;
// dynamic programming => save already identical length into array
// to understand this algo simply print identical length in every entry of the array
// i+1, j+1 then reuses information from i,j
// java initializes them already with 0
int[][] num = new int[str1.length()][str2.length()];
int maxlen = 0;
int lastSubstrBegin = 0;
int endIndex = 0;
for (int i = 0; i < str1.length(); i++) {
for (int j = 0; j < str2.length(); j++) {
if (str1.charAt(i) == str2.charAt(j)) {
if ((i == 0) || (j == 0))
num[i][j] = 1;
else
num[i][j] = 1 + num[i - 1][j - 1];
if (num[i][j] > maxlen) {
maxlen = num[i][j];
// generate substring from str1 => i
lastSubstrBegin = i - num[i][j] + 1;
endIndex = i + 1;
}
}
}
}
return new int[]{lastSubstrBegin, endIndex};
}
public static String getDefaultFavicon(String url) {
return useDomainOfFirstArg4Second(url, "/favicon.ico");
}
/**
* @param urlForDomain extract the domain from this url
* @param path this url does not have a domain
* @return
*/
public static String useDomainOfFirstArg4Second(String urlForDomain, String path) {
try {
// See: http://stackoverflow.com/questions/1389184/building-an-absolute-url-from-a-relative-url-in-java
URL baseUrl = new URL(urlForDomain);
URL relativeurl = new URL(baseUrl, path);
return relativeurl.toString();
} catch (MalformedURLException ex) {
return path;
}
}
public static String extractHost(String url) {
return extractDomain(url, false);
}
public static String extractDomain(String url, boolean aggressive) {
if (url.startsWith("http://"))
url = url.substring("http://".length());
else if (url.startsWith("https://"))
url = url.substring("https://".length());
if (aggressive) {
if (url.startsWith("www."))
url = url.substring("www.".length());
// strip mobile from start
if (url.startsWith("m."))
url = url.substring("m.".length());
}
int slashIndex = url.indexOf("/");
if (slashIndex > 0)
url = url.substring(0, slashIndex);
return url;
}
public static boolean isVideoLink(String url) {
url = extractDomain(url, true);
return url.startsWith("youtube.com") || url.startsWith("video.yahoo.com")
|| url.startsWith("vimeo.com") || url.startsWith("blip.tv");
}
public static boolean isVideo(String url) {
return url.endsWith(".mpeg") || url.endsWith(".mpg") || url.endsWith(".avi") || url.endsWith(".mov")
|| url.endsWith(".mpg4") || url.endsWith(".mp4") || url.endsWith(".flv") || url.endsWith(".wmv");
}
public static boolean isAudio(String url) {
return url.endsWith(".mp3") || url.endsWith(".ogg") || url.endsWith(".m3u") || url.endsWith(".wav");
}
public static boolean isDoc(String url) {
return url.endsWith(".pdf") || url.endsWith(".ppt") || url.endsWith(".doc")
|| url.endsWith(".swf") || url.endsWith(".rtf") || url.endsWith(".xls");
}
public static boolean isPackage(String url) {
return url.endsWith(".gz") || url.endsWith(".tgz") || url.endsWith(".zip")
|| url.endsWith(".rar") || url.endsWith(".deb") || url.endsWith(".rpm") || url.endsWith(".7z");
}
public static boolean isApp(String url) {
return url.endsWith(".exe") || url.endsWith(".bin") || url.endsWith(".bat") || url.endsWith(".dmg");
}
public static boolean isImage(String url) {
return url.endsWith(".png") || url.endsWith(".jpeg") || url.endsWith(".gif")
|| url.endsWith(".jpg") || url.endsWith(".bmp") || url.endsWith(".ico") || url.endsWith(".eps");
}
/**
* @see "http://blogs.sun.com/CoreJavaTechTips/entry/cookie_handling_in_java_se"
*/
public static void enableCookieMgmt() {
CookieManager manager = new CookieManager();
manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
CookieHandler.setDefault(manager);
}
/**
* @see "http://stackoverflow.com/questions/2529682/setting-user-agent-of-a-java-urlconnection"
*/
public static void enableUserAgentOverwrite() {
System.setProperty("http.agent", "");
}
public static String getUrlFromUglyGoogleRedirect(String url) {
if (url.startsWith("http://www.google.com/url?")) {
url = url.substring("http://www.google.com/url?".length());
String arr[] = urlDecode(url).split("\\&");
for (String str : arr) {
if (str.startsWith("q="))
return str.substring("q=".length());
}
}
return null;
}
public static String getUrlFromUglyFacebookRedirect(String url) {
if (url.startsWith("http://www.facebook.com/l.php?u=")) {
url = url.substring("http://www.facebook.com/l.php?u=".length());
return urlDecode(url);
}
return null;
}
public static String urlEncode(String str) {
try {
return URLEncoder.encode(str, UTF8);
} catch (UnsupportedEncodingException ex) {
return str;
}
}
public static String urlDecode(String str) {
try {
return URLDecoder.decode(str, UTF8);
} catch (UnsupportedEncodingException ex) {
return str;
}
}
/**
* Popular sites uses the #! to indicate the importance of the following
* chars. Ugly but true. Such as: facebook, twitter, gizmodo, ...
*/
public static String removeHashbang(String url) {
return url.replaceFirst("#!", "");
}
public static String printNode(Element root) {
return printNode(root, 0);
}
public static String printNode(Element root, int indentation) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentation; i++) {
sb.append(' ');
}
sb.append(root.tagName());
sb.append(':');
sb.append(root.ownText());
sb.append('\n');
for (Element el : root.children()) {
sb.append(printNode(el, indentation + 1));
sb.append('\n');
}
return sb.toString();
}
public static String estimateDate(String url) {
int index = url.indexOf("://");
if (index > 0)
url = url.substring(index + 3);
int year = -1;
int yearCounter = -1;
int month = -1;
int monthCounter = -1;
int day = -1;
String strs[] = url.split("/");
for (int counter = 0; counter < strs.length; counter++) {
String str = strs[counter];
if (str.length() == 4) {
try {
year = Integer.parseInt(str);
} catch (Exception ex) {
continue;
}
if (year < 1970 || year > 3000) {
year = -1;
continue;
}
yearCounter = counter;
} else if (str.length() == 2) {
if (monthCounter < 0 && counter == yearCounter + 1) {
try {
month = Integer.parseInt(str);
} catch (Exception ex) {
continue;
}
if (month < 1 || month > 12) {
month = -1;
continue;
}
monthCounter = counter;
} else if (counter == monthCounter + 1) {
try {
day = Integer.parseInt(str);
} catch (Exception ignored) {
// ignored
}
if (day < 1 || day > 31) {
day = -1;
continue;
}
break;
}
}
}
if (year < 0)
return null;
StringBuilder str = new StringBuilder(year);
if (month < 1)
return str.toString();
str.append('/');
if (month < 10)
str.append('0');
str.append(month);
if (day < 1)
return str.toString();
str.append('/');
if (day < 10)
str.append('0');
str.append(day);
return str.toString();
}
public static String completeDate(String dateStr) {
if (dateStr == null)
return null;
int index = dateStr.indexOf('/');
if (index > 0) {
index = dateStr.indexOf('/', index + 1);
if (index > 0)
return dateStr;
else
return dateStr + "/01";
}
return dateStr + "/01/01";
}
// with the help of http://stackoverflow.com/questions/1828775/httpclient-and-ssl
public static void enableAnySSL() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static class DefaultTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
public static int countLetters(String str) {
int len = str.length();
int chars = 0;
for (int i = 0; i < len; i++) {
if (Character.isLetter(str.charAt(i)))
chars++;
}
return chars;
}
}
@@ -0,0 +1,22 @@
package acr.browser.lightning.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
public static boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null)
return false;
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnected();
}
}
@@ -0,0 +1,174 @@
package acr.browser.lightning.utils;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
public class AdBlock {
private static final String TAG = "AdBlock";
private static final String BLOCKED_DOMAINS_LIST_FILE_NAME = "hosts.txt";
private static final String LOCAL_IP_V4 = "127.0.0.1";
private static final String LOCAL_IP_V4_ALT = "0.0.0.0";
private static final String LOCAL_IP_V6 = "::1";
private static final String LOCALHOST = "localhost";
private static final String COMMENT = "#";
private static final String TAB = "\t";
private static final String SPACE = " ";
private static final String EMPTY = "";
private final Set<String> mBlockedDomainsList = new HashSet<>();
private boolean mBlockAds;
private static final Locale mLocale = Locale.getDefault();
private static AdBlock mInstance;
public static AdBlock getInstance(Context context) {
if (mInstance == null) {
mInstance = new AdBlock(context);
}
return mInstance;
}
private AdBlock(Context context) {
if (mBlockedDomainsList.isEmpty() && Constants.FULL_VERSION) {
loadHostsFile(context);
}
mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled();
}
public void updatePreference() {
mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled();
}
private void loadBlockedDomainsList(final Context context) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
AssetManager asset = context.getAssets();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
asset.open(BLOCKED_DOMAINS_LIST_FILE_NAME)));
String line;
while ((line = reader.readLine()) != null) {
mBlockedDomainsList.add(line.trim().toLowerCase(mLocale));
}
} catch (IOException e) {
Log.wtf(TAG, "Reading blocked domains list from file '"
+ BLOCKED_DOMAINS_LIST_FILE_NAME + "' failed.", e);
}
}
});
thread.start();
}
/**
* a method that determines if the given URL is an ad or not. It performs
* a search of the URL's domain on the blocked domain hash set.
* @param url the URL to check for being an ad
* @return true if it is an ad, false if it is not an ad
*/
public boolean isAd(String url) {
if (!mBlockAds || url == null) {
return false;
}
String domain;
try {
domain = getDomainName(url);
} catch (URISyntaxException e) {
Log.d(TAG, "URL '" + url + "' is invalid", e);
return false;
}
boolean isOnBlacklist = mBlockedDomainsList.contains(domain.toLowerCase(mLocale));
if (isOnBlacklist) {
Log.d(TAG, "URL '" + url + "' is an ad");
}
return isOnBlacklist;
}
/**
* Returns the probable domain name for a given URL
* @param url the url to parse
* @return returns the domain
* @throws URISyntaxException throws an exception if the string cannot form a URI
*/
private static String getDomainName(String url) throws URISyntaxException {
int index = url.indexOf('/', 8);
if (index != -1) {
url = url.substring(0, index);
}
URI uri = new URI(url);
String domain = uri.getHost();
if (domain == null) {
return url;
}
return domain.startsWith("www.") ? domain.substring(4) : domain;
}
/**
* This method reads through a hosts file and extracts the domains that should
* be redirected to localhost (a.k.a. IP address 127.0.0.1). It can handle files that
* simply have a list of hostnames to block, or it can handle a full blown hosts file.
* It will strip out comments, references to the base IP address and just extract the
* domains to be used
* @param context the context needed to read the file
*/
private void loadHostsFile(final Context context) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
AssetManager asset = context.getAssets();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(
asset.open(BLOCKED_DOMAINS_LIST_FILE_NAME)));
String line;
while ((line = reader.readLine()) != null) {
if (!line.isEmpty() && !line.startsWith(COMMENT)) {
line = line.replace(LOCAL_IP_V4, EMPTY)
.replace(LOCAL_IP_V4_ALT, EMPTY)
.replace(LOCAL_IP_V6, EMPTY)
.replace(TAB, EMPTY);
int comment = line.indexOf(COMMENT);
if (comment >= 0) {
line = line.substring(0, comment);
}
line = line.trim();
if (!line.isEmpty() && !line.equals(LOCALHOST)) {
while (line.contains(SPACE)) {
int space = line.indexOf(SPACE);
String host = line.substring(0, space);
mBlockedDomainsList.add(host.trim());
line = line.substring(space, line.length()).trim();
}
mBlockedDomainsList.add(line.trim());
}
}
}
} catch (IOException e) {
Log.wtf(TAG, "Reading blocked domains list from file '"
+ BLOCKED_DOMAINS_LIST_FILE_NAME + "' failed.", e);
} finally {
Utils.close(reader);
}
}
});
thread.start();
}
}
@@ -0,0 +1,106 @@
package acr.browser.lightning.utils;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.util.Log;
import android.webkit.WebView;
import java.net.URISyntaxException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import acr.browser.lightning.controller.BrowserController;
public class IntentUtils {
private final Activity mActivity;
private static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile("(?i)"
+ // switch on case insensitive matching
'('
+ // begin group for schema
"(?:http|https|file):\\/\\/" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)"
+ ')' + "(.*)");
public IntentUtils(BrowserController controller) {
mActivity = controller.getActivity();
}
public boolean startActivityForUrl(WebView tab, String url) {
Intent intent;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException ex) {
Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
return false;
}
if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
String packagename = intent.getPackage();
if (packagename != null) {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:"
+ packagename));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
mActivity.startActivity(intent);
return true;
} else {
return false;
}
}
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setComponent(null);
if (tab != null) {
intent.putExtra(mActivity.getPackageName() + ".Origin", 1);
}
Matcher m = ACCEPTED_URI_SCHEMA.matcher(url);
if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
return false;
}
try {
if (mActivity.startActivityIfNeeded(intent, -1)) {
return true;
}
} catch (ActivityNotFoundException ex) {
ex.printStackTrace();
}
return false;
}
/**
* Search for intent handlers that are specific to this URL aka, specialized
* apps like google maps or youtube
*/
private boolean isSpecializedHandlerAvailable(Intent intent) {
PackageManager pm = mActivity.getPackageManager();
List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.isEmpty()) {
return false;
}
for (ResolveInfo resolveInfo : handlers) {
IntentFilter filter = resolveInfo.filter;
if (filter == null) {
// No intent filter matches this intent?
// Error on the side of staying in the browser, ignore
continue;
}
// NOTICE: Use of && instead of || will cause the browser
// to launch a new intent for every URL, using OR only
// launches a new one if there is a non-browser app that
// can handle it.
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) {
// Generic handler, skip
continue;
}
return true;
}
return false;
}
}
@@ -0,0 +1,75 @@
package acr.browser.lightning.utils;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Copyright 8/22/2015 Anthony Restaino
*/
public class PermissionsManager {
private static PermissionsManager mInstance;
private Set<String> mPendingRequests = new HashSet<>();
public static PermissionsManager getInstance() {
if (mInstance == null) {
mInstance = new PermissionsManager();
}
return mInstance;
}
public void requestPermissionsIfNecessary(Activity activity, @NonNull String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) {
return;
}
List<String> permList = new ArrayList<>();
for (String perm : permissions) {
if (activity.checkSelfPermission(perm) != PackageManager.PERMISSION_GRANTED
&& !mPendingRequests.contains(perm)) {
permList.add(perm);
}
}
if (!permList.isEmpty()) {
String[] permsToRequest = permList.toArray(new String[permList.size()]);
mPendingRequests.addAll(permList);
activity.requestPermissions(permsToRequest, 1);
}
}
public static boolean checkPermission(Activity activity, @NonNull String permission) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
} else if (activity == null) {
return false;
}
return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
public static boolean checkPermissions(Activity activity, @NonNull String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
} else if (activity == null) {
return false;
}
boolean permissionsNecessary = true;
for (String perm : permissions) {
permissionsNecessary &= activity.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED;
}
return permissionsNecessary;
}
public void notifyPermissionsChange(String[] permissions) {
for (String perm : permissions) {
mPendingRequests.remove(perm);
}
}
}
@@ -0,0 +1,123 @@
package acr.browser.lightning.utils;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.AttrRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.TypedValue;
import android.widget.ImageView;
import acr.browser.lightning.R;
public class ThemeUtils {
private static final TypedValue sTypedValue = new TypedValue();
public static int getPrimaryColor(@NonNull Context context) {
return getColor(context, R.attr.colorPrimary);
}
public static int getPrimaryColorDark(@NonNull Context context) {
return getColor(context, R.attr.colorPrimaryDark);
}
public static int getAccentColor(@NonNull Context context) {
return getColor(context, R.attr.colorAccent);
}
private static int getColor(@NonNull Context context, @AttrRes int resource) {
TypedArray a = context.obtainStyledAttributes(sTypedValue.data, new int[]{resource});
int color = a.getColor(0, 0);
a.recycle();
return color;
}
public static int getIconLightThemeColor(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return context.getResources().getColor(R.color.icon_light_theme, context.getTheme());
}
return context.getResources().getColor(R.color.icon_light_theme);
}
public static int getIconDarkThemeColor(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return context.getResources().getColor(R.color.icon_dark_theme, context.getTheme());
}
return context.getResources().getColor(R.color.icon_dark_theme);
}
public static void themeImageView(ImageView icon, Context context, boolean dark) {
int color = dark ? getIconDarkThemeColor(context) : getIconLightThemeColor(context);
icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
public static Bitmap getThemedBitmap(Context context, @DrawableRes int res, boolean dark) {
int color = dark ? getIconDarkThemeColor(context) : getIconLightThemeColor(context);
Bitmap sourceBitmap = BitmapFactory.decodeResource(context.getResources(), res);
Bitmap resultBitmap = Bitmap.createBitmap(sourceBitmap.getWidth(), sourceBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Paint p = new Paint();
ColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
p.setColorFilter(filter);
Canvas canvas = new Canvas(resultBitmap);
canvas.drawBitmap(sourceBitmap, 0, 0, p);
sourceBitmap.recycle();
return resultBitmap;
}
@Nullable
public static Drawable getThemedDrawable(@NonNull Context context, @DrawableRes int res, boolean dark) {
int color = dark ? getIconDarkThemeColor(context) : getIconLightThemeColor(context);
final Drawable drawable;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = context.getResources().getDrawable(res);
} else {
drawable = context.getDrawable(res);
}
if (drawable == null)
return null;
drawable.mutate();
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
return drawable;
}
@Nullable
public static Drawable getLightThemedDrawable(@NonNull Context context, @DrawableRes int res) {
final Drawable drawable;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = context.getResources().getDrawable(res);
} else {
drawable = context.getDrawable(res);
}
if (drawable == null)
return null;
drawable.mutate();
drawable.setColorFilter(getIconLightThemeColor(context), PorterDuff.Mode.SRC_IN);
return drawable;
}
public static ColorDrawable getSelectedBackground(@NonNull Context context, boolean dark) {
Resources res = context.getResources();
int color;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
color = (dark) ? res.getColor(R.color.divider_dark, context.getTheme()) :
res.getColor(R.color.divider_light, context.getTheme());
} else {
color = (dark) ? res.getColor(R.color.divider_dark) :
res.getColor(R.color.divider_light);
}
return new ColorDrawable(color);
}
}
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package acr.browser.lightning.utils;
import android.net.Uri;
import android.util.Patterns;
import android.webkit.URLUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility methods for Url manipulation
*/
public class UrlUtils {
static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
"(?i)" + // switch on case insensitive matching
"(" + // begin group for schema
"(?:http|https|file):\\/\\/" +
"|(?:inline|data|about|javascript):" +
"|(?:.*:.*@)" +
")" +
"(.*)");
// Google search
public final static String QUERY_PLACE_HOLDER = "%s";
// Regular expression to strip http:// and optionally
// the trailing slash
private static final Pattern STRIP_URL_PATTERN =
Pattern.compile("^http://(.*?)/?$");
private UrlUtils() { /* cannot be instantiated */ }
/**
* Strips the provided url of preceding "http://" and any trailing "/". Does not
* strip "https://". If the provided string cannot be stripped, the original string
* is returned.
* <p/>
* TODO: Put this in TextUtils to be used by other packages doing something similar.
*
* @param url a url to strip, like "http://www.google.com/"
* @return a stripped url like "www.google.com", or the original string if it could
* not be stripped
*/
public static String stripUrl(String url) {
if (url == null) return null;
Matcher m = STRIP_URL_PATTERN.matcher(url);
if (m.matches()) {
return m.group(1);
} else {
return url;
}
}
/**
* Attempts to determine whether user input is a URL or search
* terms. Anything with a space is passed to search if canBeSearch is true.
* <p/>
* Converts to lowercase any mistakenly uppercased schema (i.e.,
* "Http://" converts to "http://"
*
* @param canBeSearch If true, will return a search url if it isn't a valid
* URL. If false, invalid URLs will return null
* @return Original or modified URL
*/
public static String smartUrlFilter(String url, boolean canBeSearch, String searchUrl) {
String inUrl = url.trim();
boolean hasSpace = inUrl.indexOf(' ') != -1;
Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
if (matcher.matches()) {
// force scheme to lowercase
String scheme = matcher.group(1);
String lcScheme = scheme.toLowerCase();
if (!lcScheme.equals(scheme)) {
inUrl = lcScheme + matcher.group(2);
}
if (hasSpace && Patterns.WEB_URL.matcher(inUrl).matches()) {
inUrl = inUrl.replace(" ", "%20");
}
return inUrl;
}
if (!hasSpace) {
if (Patterns.WEB_URL.matcher(inUrl).matches()) {
return URLUtil.guessUrl(inUrl);
}
}
if (canBeSearch) {
return URLUtil.composeSearchUrl(inUrl,
searchUrl, QUERY_PLACE_HOLDER);
}
return null;
}
/* package */
static String fixUrl(String inUrl) {
// FIXME: Converting the url to lower case
// duplicates functionality in smartUrlFilter().
// However, changing all current callers of fixUrl to
// call smartUrlFilter in addition may have unwanted
// consequences, and is deferred for now.
int colon = inUrl.indexOf(':');
boolean allLower = true;
for (int index = 0; index < colon; index++) {
char ch = inUrl.charAt(index);
if (!Character.isLetter(ch)) {
break;
}
allLower &= Character.isLowerCase(ch);
if (index == colon - 1 && !allLower) {
inUrl = inUrl.substring(0, colon).toLowerCase()
+ inUrl.substring(colon);
}
}
if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
return inUrl;
if (inUrl.startsWith("http:") ||
inUrl.startsWith("https:")) {
if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
inUrl = inUrl.replaceFirst("/", "//");
} else inUrl = inUrl.replaceFirst(":", "://");
}
return inUrl;
}
// Returns the filtered URL. Cannot return null, but can return an empty string
/* package */
static String filteredUrl(String inUrl) {
if (inUrl == null) {
return "";
}
if (inUrl.startsWith("content:")
|| inUrl.startsWith("browser:")) {
return "";
}
return inUrl;
}
}
@@ -0,0 +1,312 @@
/*
* Copyright 2014 A.C.R. Development
*/
package acr.browser.lightning.utils;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.webkit.URLUtil;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.download.DownloadHandler;
public final class Utils {
public static void downloadFile(final Activity activity, final String url,
final String userAgent, final String contentDisposition) {
String fileName = URLUtil.guessFileName(url, null, null);
DownloadHandler.onDownloadStart(activity, url, userAgent, contentDisposition, null
);
Log.i(Constants.TAG, "Downloading" + fileName);
}
public static Intent newEmailIntent(String address, String subject,
String body, String cc) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{address});
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_CC, cc);
intent.setType("message/rfc822");
return intent;
}
public static void createInformativeDialog(Context context, @StringRes int title, @StringRes int message) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
builder.setMessage(message)
.setCancelable(true)
.setPositiveButton(context.getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
}
});
AlertDialog alert = builder.create();
alert.show();
}
public static void showSnackbar(@NonNull Activity activity, @StringRes int resource) {
View view = activity.findViewById(android.R.id.content);
if (view == null) return;
Snackbar.make(view, resource, Snackbar.LENGTH_SHORT).show();
}
public static void showSnackbar(@NonNull Activity activity, String message) {
View view = activity.findViewById(android.R.id.content);
if (view == null) return;
Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show();
}
/**
* Converts Density Pixels (DP) to Pixels (PX)
*
* @param dp the number of density pixels to convert
* @return the number of pixels
*/
public static int dpToPx(int dp) {
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
return (int) (dp * metrics.density + 0.5f);
}
public static String getDomainName(String url) {
if (url == null || url.isEmpty()) return "";
boolean ssl = url.startsWith(Constants.HTTPS);
int index = url.indexOf('/', 8);
if (index != -1) {
url = url.substring(0, index);
}
URI uri;
String domain;
try {
uri = new URI(url);
domain = uri.getHost();
} catch (URISyntaxException e) {
e.printStackTrace();
domain = null;
}
if (domain == null || domain.isEmpty()) {
return url;
}
if (ssl)
return Constants.HTTPS + domain;
else
return domain.startsWith("www.") ? domain.substring(4) : domain;
}
public static String getProtocol(String url) {
int index = url.indexOf('/');
return url.substring(0, index + 2);
}
public static String[] getArray(String input) {
return input.split(Constants.SEPARATOR);
}
public static void trimCache(Context context) {
try {
File dir = context.getCacheDir();
if (dir != null && dir.isDirectory()) {
deleteDir(dir);
}
} catch (Exception ignored) {
}
}
public static boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
boolean success = deleteDir(new File(dir, aChildren));
if (!success) {
return false;
}
}
}
// The directory is now empty so delete it
return dir != null && dir.delete();
}
/**
* Creates and returns a new favicon which is the same as the provided
* favicon but with horizontal or vertical padding of 4dp
*
* @param bitmap is the bitmap to pad.
* @return the padded bitmap.
*/
public static Bitmap padFavicon(Bitmap bitmap) {
int padding = Utils.dpToPx(4);
Bitmap paddedBitmap = Bitmap.createBitmap(bitmap.getWidth() + padding, bitmap.getHeight()
+ padding, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(paddedBitmap);
canvas.drawARGB(0x00, 0x00, 0x00, 0x00); // this represents white color
canvas.drawBitmap(bitmap, padding / 2, padding / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
return paddedBitmap;
}
public static boolean isColorTooDark(int color) {
final byte RED_CHANNEL = 16;
final byte GREEN_CHANNEL = 8;
//final byte BLUE_CHANNEL = 0;
int r = ((int) ((float) (color >> RED_CHANNEL & 0xff) * 0.3f)) & 0xff;
int g = ((int) ((float) (color >> GREEN_CHANNEL & 0xff) * 0.59)) & 0xff;
int b = ((int) ((float) (color /* >> BLUE_CHANNEL */ & 0xff) * 0.11)) & 0xff;
int gr = (r + g + b) & 0xff;
int gray = gr /* << BLUE_CHANNEL */ + (gr << GREEN_CHANNEL) + (gr << RED_CHANNEL);
return gray < 0x727272;
}
public static int mixTwoColors(int color1, int color2, float amount) {
final byte ALPHA_CHANNEL = 24;
final byte RED_CHANNEL = 16;
final byte GREEN_CHANNEL = 8;
//final byte BLUE_CHANNEL = 0;
final float inverseAmount = 1.0f - amount;
int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) + ((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) + ((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int b = ((int) (((float) (color1 & 0xff) * amount) + ((float) (color2 & 0xff) * inverseAmount))) & 0xff;
return 0xff << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b;
}
@SuppressLint("SimpleDateFormat")
public static File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + '_';
File storageDir = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
return File.createTempFile(imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
}
/**
* Checks if flash player is installed
*
* @param context the context needed to obtain the PackageManager
* @return true if flash is installed, false otherwise
*/
public static boolean isFlashInstalled(Context context) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo("com.adobe.flashplayer", 0);
if (ai != null) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return false;
}
/**
* Quietly closes a closeable object like an InputStream or OutputStream without
* throwing any errors or requiring you do do any checks.
*
* @param closeable the object to close
*/
public static void close(Closeable closeable) {
if (closeable == null)
return;
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Drawable getDrawable(Context context, @DrawableRes int res) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return context.getDrawable(res);
} else {
return context.getResources().getDrawable(res);
}
}
/**
* Draws the trapezoid background for the horizontal tabs on a canvas object using
* the specified color.
*
* @param canvas the canvas to draw upon
* @param color the color to use to draw the tab
*/
public static void drawTrapezoid(Canvas canvas, int color, boolean withShader) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
// paint.setFilterBitmap(true);
paint.setAntiAlias(true);
paint.setDither(true);
if (withShader) {
paint.setShader(new LinearGradient(0, 0.9f * canvas.getHeight(),
0, canvas.getHeight(),
color, mixTwoColors(Color.BLACK, color, 0.5f),
Shader.TileMode.CLAMP));
} else {
paint.setShader(null);
}
int width = canvas.getWidth();
int height = canvas.getHeight();
double radians = Math.PI / 3;
int base = (int) (height / Math.tan(radians));
Path wallpath = new Path();
wallpath.reset();
wallpath.moveTo(0, height);
wallpath.lineTo(width, height);
wallpath.lineTo(width - base, 0);
wallpath.lineTo(base, 0);
wallpath.close();
canvas.drawPath(wallpath, paint);
}
}
@@ -0,0 +1,52 @@
package acr.browser.lightning.utils;
import android.content.Context;
import android.os.Build;
import android.provider.Browser;
import android.support.annotation.NonNull;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebIconDatabase;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewDatabase;
import acr.browser.lightning.database.HistoryDatabase;
/**
* Copyright 8/4/2015 Anthony Restaino
*/
public class WebUtils {
public static void clearCookies(@NonNull Context context) {
CookieManager c = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
c.removeAllCookies(null);
} else {
CookieSyncManager.createInstance(context);
c.removeAllCookie();
}
}
public static void clearWebStorage() {
WebStorage.getInstance().deleteAllData();
}
public static void clearHistory(@NonNull Context context) {
HistoryDatabase.getInstance(context).deleteHistory();
WebViewDatabase m = WebViewDatabase.getInstance(context);
m.clearFormData();
m.clearHttpAuthUsernamePassword();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
m.clearUsernamePassword();
WebIconDatabase.getInstance().removeAllIcons();
}
Utils.trimCache(context);
}
public static void clearCache(WebView view) {
if (view == null) return;
view.clearCache(true);
}
}
@@ -0,0 +1,221 @@
package acr.browser.lightning.view;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
import acr.browser.lightning.R;
/**
* Copyright 11/4/2014 Anthony Restaino
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class AnimatedProgressBar extends LinearLayout {
private int mProgress = 0;
private boolean mBidirectionalAnimate = true;
private int mDrawWidth = 0;
private int mProgressColor;
public AnimatedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AnimatedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* Initialize the AnimatedProgressBar
*
* @param context is the context passed by the constructor
* @param attrs is the attribute set passed by the constructor
*/
private void init(final Context context, AttributeSet attrs) {
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AnimatedProgressBar, 0, 0);
int backgroundColor;
try { // Retrieve the style of the progress bar that the user hopefully set
int DEFAULT_BACKGROUND_COLOR = 0x424242;
int DEFAULT_PROGRESS_COLOR = 0x2196f3;
backgroundColor = array.getColor(R.styleable.AnimatedProgressBar_backgroundColor, DEFAULT_BACKGROUND_COLOR);
mProgressColor = array.getColor(R.styleable.AnimatedProgressBar_progressColor, DEFAULT_PROGRESS_COLOR);
mBidirectionalAnimate = array.getBoolean(R.styleable.AnimatedProgressBar_bidirectionalAnimate, false);
} finally {
array.recycle();
}
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.animated_progress_bar, this, true);
this.setBackgroundColor(backgroundColor); // set the background color for this view
}
/**
* Returns the current progress value between 0 and 100
*
* @return progress of the view
*/
public int getProgress() {
return mProgress;
}
private final Paint mPaint = new Paint();
private final Rect mRect = new Rect();
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(10);
mRect.right = mRect.left + mDrawWidth;
canvas.drawRect(mRect, mPaint);
}
/**
* sets the progress as an integer value between 0 and 100.
* Values above or below that interval will be adjusted to their
* nearest value within the interval, i.e. setting a value of 150 will have
* the effect of setting the progress to 100. You cannot trick us.
*
* @param progress an integer between 0 and 100
*/
public void setProgress(int progress) {
if (progress > 100) { // progress cannot be greater than 100
progress = 100;
} else if (progress < 0) { // progress cannot be less than 0
progress = 0;
}
if (this.getAlpha() < 1.0f) {
fadeIn();
}
int mWidth = this.getMeasuredWidth();
// Set the drawing bounds for the ProgressBar
mRect.left = 0;
mRect.top = 0;
mRect.bottom = this.getBottom() - this.getTop();
if (progress < mProgress && !mBidirectionalAnimate) { // if the we only animate the view in one direction
// then reset the view width if it is less than the
// previous progress
mDrawWidth = 0;
} else if (progress == mProgress) { // we don't need to go any farther if the progress is unchanged
if (progress == 100) {
fadeOut();
}
return;
}
mProgress = progress; // save the progress
final int deltaWidth = (mWidth * mProgress / 100) - mDrawWidth; // calculate amount the width has to change
animateView(mDrawWidth, mWidth, deltaWidth); // animate the width change
}
/**
* private method used to create and run the animation used to change the progress
*
* @param initialWidth is the width at which the progress starts at
* @param maxWidth is the maximum width (total width of the view)
* @param deltaWidth is the amount by which the width of the progress view will change
*/
private void animateView(final int initialWidth, final int maxWidth, final int deltaWidth) {
Animation fill = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int width = initialWidth + (int) (deltaWidth * interpolatedTime);
if (width <= maxWidth) {
mDrawWidth = width;
invalidate();
}
if ((1.0f - interpolatedTime) < 0.0005) {
if (mProgress >= 100) {
fadeOut();
}
}
}
@Override
public boolean willChangeBounds() {
return false;
}
};
fill.setDuration(500);
fill.setInterpolator(new DecelerateInterpolator());
this.startAnimation(fill);
}
/**
* fades in the progress bar
*/
private void fadeIn() {
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, "alpha", 1.0f);
fadeIn.setDuration(200);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.start();
}
/**
* fades out the progress bar
*/
private void fadeOut() {
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(this, "alpha", 0.0f);
fadeOut.setDuration(200);
fadeOut.setInterpolator(new DecelerateInterpolator());
fadeOut.start();
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
this.mProgress = bundle.getInt("progressState");
state = bundle.getParcelable("instanceState");
}
super.onRestoreInstanceState(state);
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("instanceState", super.onSaveInstanceState());
bundle.putInt("progressState", mProgress);
return bundle;
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+16
Ver Arquivo
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="@android:integer/config_mediumAnimTime"
android:fromXScale="0.85"
android:fromYScale="0.85"
android:interpolator="@android:interpolator/decelerate_cubic"
android:pivotX="50%p"
android:pivotY="50%p"
android:toXScale="1.0"
android:toYScale="1.0" />
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
+16
Ver Arquivo
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="@android:integer/config_mediumAnimTime"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:interpolator/decelerate_cubic"
android:pivotX="50%p"
android:pivotY="50%p"
android:toXScale="0.85"
android:toYScale="0.85" />
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromYDelta="0%p"
android:interpolator="@android:interpolator/accelerate_cubic"
android:toYDelta="100%p" />
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%p"
android:interpolator="@android:interpolator/decelerate_cubic"
android:toXDelta="0%p" />
<!--<alpha-->
<!--android:duration="@android:integer/config_mediumAnimTime"-->
<!--android:fromAlpha="0.0"-->
<!--android:toAlpha="1.0" />-->
</set>
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%p"
android:interpolator="@android:interpolator/decelerate_cubic"
android:toXDelta="100%p" />
<!--<alpha-->
<!--android:duration="@android:integer/config_mediumAnimTime"-->
<!--android:fromAlpha="0.0"-->
<!--android:toAlpha="1.0" />-->
</set>
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromYDelta="100%p"
android:interpolator="@android:interpolator/decelerate_cubic"
android:toYDelta="0%p" />
<!--<alpha-->
<!--android:duration="@android:integer/config_mediumAnimTime"-->
<!--android:fromAlpha="0.5"-->
<!--android:toAlpha="1.0" />-->
</set>

Antes

Largura:  |  Altura:  |  Tamanho: 171 B

Depois

Largura:  |  Altura:  |  Tamanho: 171 B

Antes

Largura:  |  Altura:  |  Tamanho: 226 B

Depois

Largura:  |  Altura:  |  Tamanho: 226 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 212 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 219 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 282 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 242 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 227 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 204 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 293 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 331 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 153 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 126 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 492 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 649 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 135 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 163 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 480 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 196 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 633 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 524 B

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais