Comparar commits
313 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 8061d8726a | |||
| 1896fa6151 | |||
| 98f0daceaa | |||
| 367c62bd39 | |||
| 04c9f75a90 | |||
| dd18526ddf | |||
| 85d92db738 | |||
| b68ad65abc | |||
| a0ade8acc9 | |||
| 676ba822af | |||
| 1e385ceb9a | |||
| 9f2f9d74eb | |||
| 68f5c4fb45 | |||
| a08d793320 | |||
| d8fc799586 | |||
| f3b0e46801 | |||
| d5e1e06d84 | |||
| 119245a5fa | |||
| ff5810c89a | |||
| 2aa03a87a6 | |||
| d6fbfeaf29 | |||
| 88f07e3ced | |||
| c301f3963a | |||
| 0a67f9e92a | |||
| c9579b9d82 | |||
| f39631bd23 | |||
| f963b4de7f | |||
| 6be3cab470 | |||
| b619a12ae3 | |||
| 58c9e820ed | |||
| 33eb739824 | |||
| 81cef51479 | |||
| 839616a4e4 | |||
| e71e09c2e8 | |||
| 25a80a86a5 | |||
| 59c720d7d8 | |||
| 7e67770617 | |||
| c4e244a82b | |||
| 29a20a7e58 | |||
| a738308a50 | |||
| 5081ee2ea6 | |||
| 29d2a5f3e5 | |||
| c39a835dc2 | |||
| 4ba7c7c5a3 | |||
| 08eedbe121 | |||
| 4a32f0dfba | |||
| f101ea34ce | |||
| 9f036410d2 | |||
| 86834fca60 | |||
| 3b13999b03 | |||
| cc78b4196f | |||
| b8b610347f | |||
| 24a99deb52 | |||
| 0f9a69ba17 | |||
| 71fcd174d7 | |||
| 399037d49b | |||
| 240c9a5a37 | |||
| 4a3362e8f1 | |||
| 7331345348 | |||
| 73e8f7c314 | |||
| aced4a3cc7 | |||
| 69deb5b5a2 | |||
| 3a76439a65 | |||
| fa7b04adee | |||
| a708050c5b | |||
| 645b98cd50 | |||
| 19103e9b2c | |||
| dce29954e1 | |||
| f061a35472 | |||
| 800d037035 | |||
| e35b368d50 | |||
| aa21657875 | |||
| f314b64e40 | |||
| 6c9d23488b | |||
| 41cb2c4d27 | |||
| 969cab81e7 | |||
| b98dd272c0 | |||
| 67b506c5f8 | |||
| e9af39a20b | |||
| 5950c66567 | |||
| 56c9934145 | |||
| 1d5b904c64 | |||
| 7d9f382333 | |||
| c60b4389a0 | |||
| 6b80df1d27 | |||
| b60f555553 | |||
| 2127863465 | |||
| d47a86d9b5 | |||
| a03444f4d0 | |||
| 86b8eaee12 | |||
| d8b8d2c047 | |||
| 9dc9634299 | |||
| e13e2dd006 | |||
| 4c7bb196dd | |||
| a2f2fbc82b | |||
| 80d765b61c | |||
| b478c1ea98 | |||
| 7e5dbbc811 | |||
| 2eec8be4ce | |||
| 0c10efec00 | |||
| 2cbe2e80e7 | |||
| e7029a7b64 | |||
| 1e0770057e | |||
| 810483ec74 | |||
| a0b2197d8f | |||
| a5a20eebbd | |||
| 6e8da9f6d3 | |||
| c74b84d070 | |||
| e9203f20b3 | |||
| 4a38511218 | |||
| 3517435956 | |||
| 8bb38d1a90 | |||
| 111d594c6b | |||
| 6c2a557135 | |||
| 46fbc56604 | |||
| 0beec60b7f | |||
| 1de67105d9 | |||
| eacadcebba | |||
| 9f8dff8c5d | |||
| b400ef0647 | |||
| 025714bd97 | |||
| 5e131a0a6c | |||
| 9677135c28 | |||
| a0268a9dfa | |||
| 28498d7d92 | |||
| 2f3655045c | |||
| 459c5f8ff0 | |||
| e335a2b936 | |||
| 051a453e7b | |||
| df903551c0 | |||
| 99a23c2eef | |||
| fa1994c8b2 | |||
| f5fcc2e62b | |||
| cb27bf8afa | |||
| 51fc8d7f85 | |||
| 13d85c0f90 | |||
| 15fcb2ed62 | |||
| e6174e82d2 | |||
| f8c2d0096d | |||
| db734dfa7d | |||
| e40f1d44a5 | |||
| e7188c5985 | |||
| 8738af72f0 | |||
| b02f726a32 | |||
| 4868cf0cc6 | |||
| fa77fe228a | |||
| e65e062652 | |||
| 27513bd94e | |||
| d39bf65880 | |||
| 8e32668305 | |||
| ce3923d336 | |||
| 38bce6a9a0 | |||
| 0c88fbaec8 | |||
| 5c1b765616 | |||
| 79d32127f4 | |||
| e31f533bbb | |||
| 89515e4420 | |||
| 35e57fa1a6 | |||
| 7789fc7480 | |||
| 0d12624cbb | |||
| 837dd64c2c | |||
| d3ab62dce6 | |||
| 4fb1a50f03 | |||
| 2b59ea1906 | |||
| 419e36ffd1 | |||
| 37afaf7035 | |||
| b552dc116f | |||
| 128ebfab14 | |||
| 9ebd876f27 | |||
| 03fe3cdee5 | |||
| e042830e17 | |||
| 4711fa696a | |||
| f4078cce33 | |||
| 9d92a97fe2 | |||
| c4c50467dd | |||
| 792cb0d4d6 | |||
| 981d263331 | |||
| 15c5703a3b | |||
| a6a1baf41b | |||
| 38d44b8c2f | |||
| 56bd6e07fc | |||
| 6125e385df | |||
| 532860245d | |||
| 68a9b1de7e | |||
| 97e2e8d79a | |||
| be3a59c74c | |||
| 17a63733fe | |||
| 3f5c08bb5a | |||
| 6f30103cd9 | |||
| 2d347074a6 | |||
| bd8d2ee0b8 | |||
| c1baab8c9c | |||
| 4e5eac4d5b | |||
| cc6d7c7aa9 | |||
| 4822996da1 | |||
| 546dbe4f8c | |||
| 013011ef09 | |||
| 344b662619 | |||
| 93f8b971e7 | |||
| b135e27fd9 | |||
| 38f3a9c9df | |||
| 80d019798d | |||
| 1727d0739e | |||
| 7ab948ce3f | |||
| 8c29cb4450 | |||
| af4fa8ed2a | |||
| ab5a118947 | |||
| c8dec7b305 | |||
| c6d0a1a788 | |||
| 3239bcefe3 | |||
| 2790664866 | |||
| bad6872f69 | |||
| d9e888e8a9 | |||
| 5dafd6f815 | |||
| addaa3b2b3 | |||
| 2551b3dc27 | |||
| aedf76e3ae | |||
| 625fbb1aa9 | |||
| c75ca89775 | |||
| 763524555b | |||
| a4f0c010d1 | |||
| 1d6a445d33 | |||
| 7defcff9b1 | |||
| 5944cdc5df | |||
| 5e6a654170 | |||
| 450ba6b0fd | |||
| c87c57661f | |||
| 4699b583f0 | |||
| 9ff1614a0f | |||
| 58ca7fa303 | |||
| 1f1ed20a7e | |||
| 8b3da70d92 | |||
| f2f6f2761c | |||
| ecdf533188 | |||
| 0a4f650869 | |||
| 32f4a457bb | |||
| 0116481022 | |||
| dfb0febbe7 | |||
| b87eb5e90e | |||
| 666294834a | |||
| 3870e8c156 | |||
| 86d83b887a | |||
| 4cc65d0d77 | |||
| 095810671d | |||
| 5fb00c08c2 | |||
| 88e5a0eabb | |||
| 698693586c | |||
| f1cc80eb28 | |||
| 11d94564de | |||
| a21e2f6a7c | |||
| 971b0cd022 | |||
| d60fe82b4a | |||
| 0c57e14f05 | |||
| fa3c784722 | |||
| a4878914e2 | |||
| db20a4eeac | |||
| 10668a019b | |||
| 313f9fb105 | |||
| e7dacc9c10 | |||
| 9173e8270a | |||
| ab134a8927 | |||
| 71471f0718 | |||
| 87ee80fc8b | |||
| 29d55ec890 | |||
| 9eedc19b11 | |||
| 675df18a7d | |||
| 382cfdbc65 | |||
| ddfc5d9334 | |||
| 5edbff4f39 | |||
| 86aefa5e54 | |||
| 1e647c8e78 | |||
| 4f1a1f3aa9 | |||
| cbfacffff7 | |||
| ac48ddfbce | |||
| 489a814f54 | |||
| 43950d4f71 | |||
| 76178eddc6 | |||
| cec457fc37 | |||
| 76ab43743d | |||
| 9ffeecc584 | |||
| 0e212539e9 | |||
| 6407f1101a | |||
| 03ac2f8b42 | |||
| 35c585b3f4 | |||
| 04c4d202b2 | |||
| 6269325a44 | |||
| bd308dead7 | |||
| 920113b49a | |||
| 7f8253b470 | |||
| 17afd700d8 | |||
| 7e23135824 | |||
| 8314676918 | |||
| c2b436ecfe | |||
| a897ae4d3e | |||
| 9853804fd8 | |||
| 42de0b3ae7 | |||
| 1eed3ca948 | |||
| 8be2b62601 | |||
| a201f88906 | |||
| a292c6c776 | |||
| b4714a17c8 | |||
| eff7e3800d | |||
| bdb36be4e4 | |||
| 3c9f63b0a6 | |||
| 61d569bb7d | |||
| 376ac564b8 | |||
| 785449fad6 | |||
| c9e2026526 | |||
| ced119f311 | |||
| 7b8088f3d4 | |||
| 762fe5b55b | |||
| fd781f4a63 | |||
| dd919513c1 |
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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>
|
||||
@@ -6,8 +6,11 @@
|
||||
|
||||
* [Download from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones)
|
||||
|
||||
####Master Branch: [](https://travis-ci.org/anthonycr/Lightning-Browser)
|
||||
####Dev Branch: [](https://travis-ci.org/anthonycr/Lightning-Browser)
|
||||
####Master Branch
|
||||
* [](https://travis-ci.org/anthonycr/Lightning-Browser)
|
||||
|
||||
####Dev Branch
|
||||
* [](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):
|
||||
````
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
*.apk
|
||||
manifest-merger-release-report.txt
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
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 |
|
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 |
|
Depois Largura: | Altura: | Tamanho: 212 B |
|
Depois Largura: | Altura: | Tamanho: 219 B |
|
Depois Largura: | Altura: | Tamanho: 282 B |
|
Depois Largura: | Altura: | Tamanho: 242 B |
|
Depois Largura: | Altura: | Tamanho: 227 B |
|
Depois Largura: | Altura: | Tamanho: 204 B |
|
Depois Largura: | Altura: | Tamanho: 293 B |
|
Depois Largura: | Altura: | Tamanho: 331 B |
|
Depois Largura: | Altura: | Tamanho: 153 B |
|
Depois Largura: | Altura: | Tamanho: 126 B |
|
Depois Largura: | Altura: | Tamanho: 492 B |
|
Depois Largura: | Altura: | Tamanho: 649 B |
|
Depois Largura: | Altura: | Tamanho: 135 B |
|
Depois Largura: | Altura: | Tamanho: 163 B |
|
Depois Largura: | Altura: | Tamanho: 480 B |
|
Depois Largura: | Altura: | Tamanho: 196 B |
|
Depois Largura: | Altura: | Tamanho: 633 B |
|
Depois Largura: | Altura: | Tamanho: 524 B |