Comparar commits
311 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| dffd572afc | |||
| b7f3defd19 | |||
| e11a718d3b | |||
| a47cede6c5 | |||
| b1a8b7a0d5 | |||
| aca3b6c08a | |||
| 69dba8d5f1 | |||
| eda498c65f | |||
| d80e7e2edc | |||
| f6c818fbb5 | |||
| d59aeef3a9 | |||
| dbd7e6c2e6 | |||
| d75675e006 | |||
| 7a256707a7 | |||
| 674ebb88f9 | |||
| d6a1450bef | |||
| 99c09a9d63 | |||
| f322c570c0 | |||
| 984aa133ec | |||
| 008e61b5a8 | |||
| 6d47d7232f | |||
| e9d01dc104 | |||
| a7748ceee2 | |||
| 6e940b0a15 | |||
| cabea7e097 | |||
| 4d400f995f | |||
| bd98619d4f | |||
| 40cda1317a | |||
| e26330a5bd | |||
| dbc186db9b | |||
| d7faeaa2fc | |||
| ae6726b290 | |||
| f05312e915 | |||
| 58d8cb6a36 | |||
| 57d5298bec | |||
| bd8c439161 | |||
| f90ab177d5 | |||
| 1685a13df3 | |||
| 87ae1eb8fe | |||
| 8f230e3550 | |||
| 3e8f3b2702 | |||
| 8bcb3668c0 | |||
| b8b2bd090f | |||
| 14f08a8fef | |||
| d3ac7187bb | |||
| 2f8feead71 | |||
| ee6314f521 | |||
| caa0de84ce | |||
| 97a64401e8 | |||
| 3833fdb449 | |||
| 9c3607aa3d | |||
| 3fe2761552 | |||
| 8763f35668 | |||
| 1198aeeb4d | |||
| 6308677438 | |||
| e0ace14029 | |||
| 9ea98e13ad | |||
| a6edd3ca29 | |||
| 8132b34bbf | |||
| 486078a7d1 | |||
| 7486ebe3c4 | |||
| b2794b9d11 | |||
| f98f45225c | |||
| 2c4db0c54b | |||
| d3ead42f8e | |||
| 71a6c93551 | |||
| 737c02d6e8 | |||
| f2d2c8ed5f | |||
| 0ab302775c | |||
| fd5c26cc52 | |||
| 19b6a5bfc5 | |||
| 45df40f580 | |||
| a1978c73b8 | |||
| 6e76e7d430 | |||
| 9b34a553ed | |||
| c9323cc7fd | |||
| 2bca40901f | |||
| b81d9a0ed8 | |||
| 79d619f82b | |||
| c684472f6e | |||
| 7f4cab1e2e | |||
| c4e5553785 | |||
| 000ecbdc25 | |||
| f1467a9a96 | |||
| 941f54d615 | |||
| 77465c83dd | |||
| d861a9a502 | |||
| c05cc7c9be | |||
| ac3f43a76f | |||
| 84627b3fae | |||
| de4fdc86e0 | |||
| c4921bbf20 | |||
| e2d46bdae2 | |||
| ba3edc00e8 | |||
| 965c5f565f | |||
| 4a21d3f4f9 | |||
| 135cf2e572 | |||
| 65c2c9c461 | |||
| 0e211ebf85 | |||
| 359a252f24 | |||
| 970ffbaca8 | |||
| b82d304d7f | |||
| 17e2640248 | |||
| 9cf0a7e11e | |||
| 12c2ada750 | |||
| ff3d94635a | |||
| 8f38b91dc1 | |||
| 4eb292f40f | |||
| dcd042b9d5 | |||
| 04e0d5650f | |||
| b93413f9a3 | |||
| 416dc4594d | |||
| c19dbe09bb | |||
| 425392456c | |||
| 29836bd98a | |||
| f73f82030f | |||
| 600034b6fa | |||
| ac107d6704 | |||
| cb52aa0065 | |||
| db52a94d8c | |||
| 076b74e867 | |||
| f6b60894f6 | |||
| 24385c4334 | |||
| 22960c9bd6 | |||
| 930880b339 | |||
| a434c0af68 | |||
| c95f1f86e9 | |||
| 68a4475ec7 | |||
| da4985d4de | |||
| 8b44ce12fa | |||
| 6084c9b478 | |||
| a24eb45ae4 | |||
| a60ae614d9 | |||
| 46b1269730 | |||
| ee52e00c83 | |||
| 5368d76218 | |||
| 35855a1c02 | |||
| c1083f6aab | |||
| 3d745cbe6e | |||
| 0185b5c1ba | |||
| 25ff01ed79 | |||
| 6aaee4ce48 | |||
| ae15c9c816 | |||
| 09679571d7 | |||
| 34327136d9 | |||
| 290e77e696 | |||
| edbd95418c | |||
| 27e01483b1 | |||
| 9b56d92922 | |||
| 7318a818c4 | |||
| e06d530528 | |||
| 057b4296d7 | |||
| f00bb77851 | |||
| cb19ce2d0a | |||
| 8d390e1d6d | |||
| 2e55ceba0c | |||
| 95dddf1992 | |||
| f56631708e | |||
| 5e4ec63c32 | |||
| c3ec66d3a2 | |||
| f48b71b390 | |||
| 39683c704e | |||
| 0a4d81f7e2 | |||
| de2d0b2ca4 | |||
| 8da11b4f08 | |||
| 2a4b636a53 | |||
| c5328c4e3d | |||
| 4f67fd8e94 | |||
| 9731250d27 | |||
| 7354e354db | |||
| 171715f40c | |||
| 6ea15553de | |||
| 8b82ac5e51 | |||
| 47341ce927 | |||
| c83a7d0058 | |||
| 734574616d | |||
| cb98ee783b | |||
| 3a95aea82f | |||
| 79b8253b21 | |||
| 1eeddaf502 | |||
| aa9a7123b2 | |||
| 6f914e9e17 | |||
| fd7cc30470 | |||
| 5059a3d01b | |||
| bfc6c3dadc | |||
| 63f2c5f798 | |||
| cc75ba1bc7 | |||
| 1eb2407543 | |||
| 006eb5e191 | |||
| 9a9a06fe7b | |||
| 23dc83fb6a | |||
| d66f5e4c17 | |||
| 6df7cdf331 | |||
| 7a0c79d11e | |||
| 4e3193bfc8 | |||
| 5dfc948fd3 | |||
| 80ac1928c1 | |||
| 441b189fad | |||
| dc188c54e3 | |||
| 3597f7f812 | |||
| 2abf75b669 | |||
| f2aa6d6e5c | |||
| 32d36f3687 | |||
| 8169294c80 | |||
| 7aaf6d1771 | |||
| 34312bb988 | |||
| 94b69fd328 | |||
| fd8cfb7031 | |||
| fc93858918 | |||
| c0ce7e74bd | |||
| 13c6594e0c | |||
| bf4c90b121 | |||
| 9f755aeed7 | |||
| e707e338ef | |||
| 7bba86d963 | |||
| 577efb76a4 | |||
| 1c96b62eb6 | |||
| 72ee377a35 | |||
| 88549bf156 | |||
| ce0e02585c | |||
| 99e4773e45 | |||
| 159053841a | |||
| 5d55e480c9 | |||
| 367f2a09d7 | |||
| a3f3fbd401 | |||
| d3867d29bd | |||
| c04cff510b | |||
| d7017789f6 | |||
| 3c51870486 | |||
| f3a9a9a46d | |||
| 741d389da4 | |||
| b8058ad345 | |||
| 0672b87ec7 | |||
| ab7273106f | |||
| b3cec67313 | |||
| 36860cc848 | |||
| 0c467d5bfe | |||
| aacec74aba | |||
| 8cb4b455cf | |||
| 5fa6b529ca | |||
| c352c331ad | |||
| c190066db2 | |||
| 06e80ad541 | |||
| 61b57cd992 | |||
| a015d810ea | |||
| 3cb576d358 | |||
| f761383fc4 | |||
| b0c1bcc028 | |||
| 1f025debd7 | |||
| c67a1108cd | |||
| 3bd08d00f3 | |||
| 38d1973a93 | |||
| 6bbc0805de | |||
| e157d45d39 | |||
| 7cec3bd6e4 | |||
| a71a8c3493 | |||
| f1da3c4147 | |||
| 42471026b3 | |||
| 6749ca39b8 | |||
| 6f36410e87 | |||
| 3615018816 | |||
| 030b839aa6 | |||
| b3f991e598 | |||
| 4f839e0866 | |||
| 05efb4eb72 | |||
| 2563e81f7a | |||
| 5c2cf07e20 | |||
| 7f965b0829 | |||
| 5c8fd41c6b | |||
| b6b2a25dbe | |||
| 9a2ed38440 | |||
| 4be31553ad | |||
| 7661ea35ee | |||
| 748397f1f0 | |||
| c65cccb25c | |||
| 2da5c4194c | |||
| 8a6ad81027 | |||
| 51f783cea4 | |||
| 74073178bf | |||
| f0c3b743d4 | |||
| 74a75d4adb | |||
| 5628433718 | |||
| 919043cad9 | |||
| 0b94eda458 | |||
| 57a25eb9dc | |||
| 965ccee8b7 | |||
| 5fd401c2c0 | |||
| 161f4100b3 | |||
| 875cd45c7b | |||
| 0ac2337ff8 | |||
| 838270b4b0 | |||
| 3fab58955c | |||
| dbf0457d79 | |||
| 5dff2db5df | |||
| d5102b5e54 | |||
| 7f07edcdf7 | |||
| b33c4caf67 | |||
| 681a76df50 | |||
| e00c82655a | |||
| 732d309888 | |||
| 3b75765d92 | |||
| b0169e73d2 | |||
| 71d6da0eee | |||
| 1b0b256ce8 | |||
| dcc67fbdb6 | |||
| 2619210f8c | |||
| 83790bec70 | |||
| 23e97306dd | |||
| 47103ba3d0 | |||
| 4eaf01e6cc | |||
| 3c9cd73bf0 |
@@ -50,3 +50,5 @@ out
|
||||
# Source:
|
||||
# https://raw.githubusercontent.com/github/gitignore/master/Android.gitignore
|
||||
# https://gitlab.com/fdroid/fdroidclient/raw/master/.gitignore
|
||||
|
||||
*.iml
|
||||
|
||||
+3
-1
@@ -2,8 +2,9 @@ language: android
|
||||
sudo: false
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-23.0.3
|
||||
- build-tools-22.0.1
|
||||
- build-tools-23.0.0
|
||||
- android-23
|
||||
- android-22
|
||||
- extra-android-support
|
||||
@@ -17,3 +18,4 @@ before_install:
|
||||
install:
|
||||
- ./gradlew
|
||||
script:
|
||||
- ./gradlew assembleDebug --stacktrace
|
||||
|
||||
+12
-8
@@ -4,7 +4,11 @@
|
||||
####Download
|
||||
* [Download APK from here](https://github.com/anthonycr/Lightning-Browser/releases)
|
||||
|
||||
* [Download from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones)
|
||||
* [Download from F-Droid](https://f-droid.org/repository/browse/?fdfilter=lightning&fdid=acr.browser.lightning)
|
||||
|
||||
* [Download Free from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones)
|
||||
|
||||
* [Download Paid from Google Play](https://play.google.com/store/apps/details?id=acr.browser.lightning)
|
||||
|
||||
####Master Branch
|
||||
* [](https://travis-ci.org/anthonycr/Lightning-Browser)
|
||||
@@ -21,15 +25,13 @@
|
||||
|
||||
* Incognito mode
|
||||
|
||||
* Flash support (prior to 4.4)
|
||||
|
||||
* Follows Google design guidelines
|
||||
|
||||
* Unique utilization of navigation drawer for tabs
|
||||
|
||||
* Google search suggestions
|
||||
|
||||
* Orbot Proxy support
|
||||
* Orbot Proxy support and I2P support
|
||||
|
||||
####Permissions
|
||||
|
||||
@@ -41,10 +43,6 @@
|
||||
|
||||
* ````ACCESS_FINE_LOCATION````: For sites like Google Maps, it is disabled by default in settings and displays a pop-up asking if a site may use your location when it is enabled
|
||||
|
||||
* ````READ_HISTORY_BOOKMARKS````: To synchronize history and bookmarks between the stock browser and Lightning
|
||||
|
||||
* ````WRITE_HISTORY_BOOKMARKS````: To synchronize history and bookmarks between the stock browser and Lightning
|
||||
|
||||
* ````ACCESS_NETWORK_STATE````: Required for the WebView to function by some OEM versions of WebKit
|
||||
|
||||
####The Code
|
||||
@@ -52,9 +50,15 @@
|
||||
* Please add translations/translation fixes as you see need
|
||||
|
||||
####Contributing
|
||||
* [The Trello Board](https://trello.com/b/Gwjx8MC3/lightning-browser)
|
||||
* 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.
|
||||
* Code Style
|
||||
* Hungarian Notation
|
||||
* Prefix member variables with 'm'
|
||||
* Prefix static member variables with 's'
|
||||
* Use 4 spaces instead of a tab (\t)
|
||||
|
||||
####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):
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
<?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>
|
||||
+64
-13
@@ -1,17 +1,27 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.0"
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
versionName "4.1.1a"
|
||||
versionName "4.3.3"
|
||||
generatedDensities = []
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
additionalParameters "--no-version-vectors"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
lightningPlus.setRoot('src/LightningPlus')
|
||||
lightningLite.setRoot('src/LightningLite')
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
@@ -25,32 +35,73 @@ android {
|
||||
proguardFiles 'proguard-project.txt'
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
lightningPlus {
|
||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||
applicationId "acr.browser.lightning"
|
||||
versionCode 80
|
||||
versionCode 88
|
||||
}
|
||||
|
||||
lightningLite {
|
||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||
applicationId "acr.browser.barebones"
|
||||
versionCode 81
|
||||
versionCode 90
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
abortOnError true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude '.readme'
|
||||
}
|
||||
}
|
||||
|
||||
dexcount {
|
||||
includeClasses = false
|
||||
includeFieldCount = false
|
||||
printAsTree = true
|
||||
orderByMethodCount = true
|
||||
verbose = 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'))
|
||||
|
||||
// support libraries
|
||||
compile 'com.android.support:palette-v7:23.4.0'
|
||||
compile 'com.android.support:appcompat-v7:23.4.0'
|
||||
compile 'com.android.support:design:23.4.0'
|
||||
compile 'com.android.support:recyclerview-v7:23.4.0'
|
||||
compile 'com.android.support:support-v4:23.4.0'
|
||||
|
||||
// html parsing for reading mode
|
||||
compile 'org.jsoup:jsoup:1.9.2'
|
||||
|
||||
// event bus
|
||||
compile 'com.squareup:otto:1.3.8'
|
||||
|
||||
// dependency injection
|
||||
compile 'com.google.dagger:dagger:2.0.2'
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
|
||||
// view binding
|
||||
compile 'com.jakewharton:butterknife:7.0.1'
|
||||
|
||||
// permissions
|
||||
compile 'com.anthonycr.grant:permissions:1.1.2'
|
||||
|
||||
// proxy support
|
||||
compile 'net.i2p.android:client:0.8'
|
||||
|
||||
// Use the following code to update the libnetcipher submodule
|
||||
// git submodule foreach git reset --hard
|
||||
// git submodule update --remote
|
||||
compile project(':libnetcipher')
|
||||
|
||||
// memory leak analysis
|
||||
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
|
||||
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
|
||||
}
|
||||
@@ -37,6 +37,12 @@
|
||||
-keep public class acr.browser.lightning.reading.*
|
||||
-keep class org.lucasr.twowayview.** { *; }
|
||||
|
||||
-keepattributes *Annotation*
|
||||
-keepclassmembers class ** {
|
||||
@com.squareup.otto.Subscribe public *;
|
||||
@com.squareup.otto.Produce public *;
|
||||
}
|
||||
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static *** d(...);
|
||||
public static *** v(...);
|
||||
@@ -44,6 +50,18 @@
|
||||
public static *** i(...);
|
||||
}
|
||||
|
||||
-keep class butterknife.** { *; }
|
||||
-dontwarn butterknife.internal.**
|
||||
-keep class **$$ViewBinder { *; }
|
||||
|
||||
-keepclasseswithmembernames class * {
|
||||
@butterknife.* <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames class * {
|
||||
@butterknife.* <methods>;
|
||||
}
|
||||
|
||||
# this will fix a force close in ReadingActivity
|
||||
-keep public class org.jsoup.** {
|
||||
public *;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* 6/4/2015 Anthony Restaino
|
||||
*/
|
||||
public class ProxyUtils {
|
||||
|
||||
private static ProxyUtils mInstance;
|
||||
|
||||
private ProxyUtils(Context context) {
|
||||
|
||||
}
|
||||
|
||||
public static ProxyUtils getInstance(Context context) {
|
||||
if (mInstance == null) {
|
||||
mInstance = new ProxyUtils(context);
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
/*
|
||||
* If Orbot/Tor or I2P is installed, prompt the user if they want to enable
|
||||
* proxying for this session
|
||||
*/
|
||||
public void checkForProxy(final Activity activity) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize WebKit Proxying
|
||||
*/
|
||||
private void initializeProxy(Activity activity) {
|
||||
|
||||
}
|
||||
|
||||
public boolean isProxyReady(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateProxySettings(Activity activity) {
|
||||
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
|
||||
}
|
||||
|
||||
public void onStart(final Activity activity) {
|
||||
|
||||
}
|
||||
|
||||
public int setProxyChoice(int choice, Activity activity) {
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -1,106 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2014 A.C.R. Development -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="acr.browser.lightning" >
|
||||
<manifest package="acr.browser.lightning"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<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.INTERNET"/>
|
||||
<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="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
|
||||
<uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"/>
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.location"
|
||||
android:required="false" />
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
android:required="false"/>
|
||||
|
||||
<application
|
||||
android:name=".activity.BrowserApp"
|
||||
android:name=".app.BrowserApp"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.LightTheme" >
|
||||
android:theme="@style/Theme.LightTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.APP_BROWSER" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.APP_BROWSER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="about" />
|
||||
<data android:scheme="javascript" />
|
||||
<data android:scheme="file"/>
|
||||
<data android:mimeType="text/html"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<data android:mimeType="application/xhtml+xml"/>
|
||||
<data android:mimeType="application/vnd.wap.xhtml+xml"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="about"/>
|
||||
<data android:scheme="javascript"/>
|
||||
</intent-filter>
|
||||
<!--
|
||||
For these schemes where any of these particular MIME types
|
||||
have been supplied, we are a good candidate.
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="inline" />
|
||||
<data android:mimeType="text/html" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="application/xhtml+xml" />
|
||||
<data android:mimeType="application/vnd.wap.xhtml+xml" />
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="inline"/>
|
||||
<data android:mimeType="text/html"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<data android:mimeType="application/xhtml+xml"/>
|
||||
<data android:mimeType="application/vnd.wap.xhtml+xml"/>
|
||||
</intent-filter>
|
||||
<!-- For viewing saved web archives. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="file" />
|
||||
<data android:mimeType="application/x-webarchive-xml" />
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:mimeType="application/x-webarchive-xml"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.WEB_SEARCH" />
|
||||
<action android:name="android.intent.action.WEB_SEARCH"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/Theme.SettingsTheme" >
|
||||
android:theme="@style/Theme.SettingsTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SETTINGS" />
|
||||
<action android:name="android.intent.action.SETTINGS"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@@ -110,22 +130,22 @@
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.DarkTheme"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.INCOGNITO" />
|
||||
<action android:name="android.intent.action.INCOGNITO"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.ReadingActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/reading_mode"
|
||||
android:theme="@style/Theme.SettingsTheme" >
|
||||
android:theme="@style/Theme.SettingsTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.READING" />
|
||||
<action android:name="android.intent.action.READING"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
Arquivo binário não exibido.
|
Antes Largura: | Altura: | Tamanho: 14 KiB Depois Largura: | Altura: | Tamanho: 23 KiB |
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,33 @@ package acr.browser.lightning.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Menu;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
|
||||
@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);
|
||||
public Observable<Void> updateCookiePreference() {
|
||||
return Observable.create(new Action<Void>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<Void> subscriber) {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
CookieSyncManager.createInstance(IncognitoActivity.this);
|
||||
}
|
||||
cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled());
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,7 +50,7 @@ public class IncognitoActivity extends BrowserActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHistory(String title, String url) {
|
||||
public void updateHistory(@Nullable String title, @NonNull String url) {
|
||||
// addItemToHistory(title, url);
|
||||
}
|
||||
|
||||
@@ -56,7 +61,11 @@ public class IncognitoActivity extends BrowserActivity {
|
||||
|
||||
@Override
|
||||
public void closeActivity() {
|
||||
closeDrawers();
|
||||
finish();
|
||||
closeDrawers(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
closeBrowser();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,33 @@ package acr.browser.lightning.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Menu;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
|
||||
@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
|
||||
public Observable<Void> updateCookiePreference() {
|
||||
return Observable.create(new Action<Void>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<Void> subscriber) {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
CookieSyncManager.createInstance(MainActivity.this);
|
||||
}
|
||||
cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled());
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,8 +39,12 @@ public class MainActivity extends BrowserActivity {
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
handleNewIntent(intent);
|
||||
super.onNewIntent(intent);
|
||||
if (isPanicTrigger(intent)) {
|
||||
panicClean();
|
||||
} else {
|
||||
handleNewIntent(intent);
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,7 +54,7 @@ public class MainActivity extends BrowserActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHistory(String title, String url) {
|
||||
public void updateHistory(@Nullable String title, @NonNull String url) {
|
||||
addItemToHistory(title, url);
|
||||
}
|
||||
|
||||
@@ -57,7 +65,14 @@ public class MainActivity extends BrowserActivity {
|
||||
|
||||
@Override
|
||||
public void closeActivity() {
|
||||
closeDrawers();
|
||||
moveTaskToBack(true);
|
||||
closeDrawers(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performExitCleanUp();
|
||||
moveTaskToBack(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
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.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -21,26 +22,42 @@ import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.OnSubscribe;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
import acr.browser.lightning.react.Schedulers;
|
||||
import acr.browser.lightning.react.Subscription;
|
||||
import acr.browser.lightning.reading.HtmlFetcher;
|
||||
import acr.browser.lightning.reading.JResult;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ReadingActivity extends AppCompatActivity {
|
||||
|
||||
private TextView mTitle;
|
||||
private TextView mBody;
|
||||
private static final String TAG = ReadingActivity.class.getSimpleName();
|
||||
|
||||
@Bind(R.id.textViewTitle)
|
||||
TextView mTitle;
|
||||
|
||||
@Bind(R.id.textViewBody)
|
||||
TextView mBody;
|
||||
|
||||
@Inject PreferenceManager mPreferences;
|
||||
|
||||
private boolean mInvert;
|
||||
private String mUrl = null;
|
||||
private PreferenceManager mPreferences;
|
||||
private int mTextSize;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private Subscription mPageLoaderSubscription;
|
||||
|
||||
private static final float XXLARGE = 30.0f;
|
||||
private static final float XLARGE = 26.0f;
|
||||
@@ -51,7 +68,9 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
|
||||
overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale);
|
||||
mInvert = mPreferences.getInvertColors();
|
||||
final int color;
|
||||
if (mInvert) {
|
||||
@@ -65,6 +84,7 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.reading_view);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
@@ -72,9 +92,6 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
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));
|
||||
@@ -131,66 +148,87 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
}
|
||||
if (getSupportActionBar() != null)
|
||||
getSupportActionBar().setTitle(Utils.getDomainName(mUrl));
|
||||
new PageLoader(this).execute(mUrl);
|
||||
mPageLoaderSubscription = loadPage(mUrl).subscribeOn(Schedulers.worker())
|
||||
.observeOn(Schedulers.main())
|
||||
.subscribe(new OnSubscribe<ReaderInfo>() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
mProgressDialog = new ProgressDialog(ReadingActivity.this);
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.setCancelable(false);
|
||||
mProgressDialog.setIndeterminate(true);
|
||||
mProgressDialog.setMessage(getString(R.string.loading));
|
||||
mProgressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@Nullable ReaderInfo item) {
|
||||
if (item == null || item.getTitle().isEmpty() || item.getBody().isEmpty()) {
|
||||
setText(getString(R.string.untitled), getString(R.string.loading_failed));
|
||||
} else {
|
||||
setText(item.getTitle(), item.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable throwable) {
|
||||
setText(getString(R.string.untitled), getString(R.string.loading_failed));
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
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();
|
||||
private static Observable<ReaderInfo> loadPage(@NonNull final String url) {
|
||||
return Observable.create(new Action<ReaderInfo>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<ReaderInfo> subscriber) {
|
||||
HtmlFetcher fetcher = new HtmlFetcher();
|
||||
try {
|
||||
JResult result = fetcher.fetchAndExtract(url, 2500, true);
|
||||
subscriber.onNext(new ReaderInfo(result.getTitle(), result.getText()));
|
||||
} catch (Exception e) {
|
||||
subscriber.onError(new Throwable("Encountered exception"));
|
||||
Log.e(TAG, "Error parsing page", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
System.gc();
|
||||
subscriber.onError(new Throwable("Out of memory"));
|
||||
Log.e(TAG, "Out of memory", e);
|
||||
}
|
||||
subscriber.onComplete();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static class ReaderInfo {
|
||||
@NonNull private final String mTitleText;
|
||||
@NonNull private final String mBodyText;
|
||||
|
||||
public ReaderInfo(@NonNull String title, @NonNull String body) {
|
||||
mTitleText = title;
|
||||
mBodyText = body;
|
||||
}
|
||||
|
||||
@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);
|
||||
@NonNull
|
||||
public String getTitle() {
|
||||
return mTitleText;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getBody() {
|
||||
return mBodyText;
|
||||
}
|
||||
}
|
||||
|
||||
private void setText(String title, String body) {
|
||||
@@ -221,6 +259,8 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mPageLoaderSubscription.unsubscribe();
|
||||
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
@@ -228,6 +268,14 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (isFinishing()) {
|
||||
overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_out_to_right);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
package acr.browser.lightning.activity;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
@@ -11,15 +12,18 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.anthonycr.grant.PermissionsManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.utils.PermissionsManager;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
|
||||
public class SettingsActivity extends ThemableSettingsActivity {
|
||||
|
||||
private static final List<String> fragments = new ArrayList<>();
|
||||
private static final List<String> mFragments = new ArrayList<>(7);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -42,15 +46,30 @@ public class SettingsActivity extends ThemableSettingsActivity {
|
||||
@Override
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
loadHeadersFromResource(R.xml.preferences_headers, target);
|
||||
fragments.clear();
|
||||
for (Header header : target) {
|
||||
fragments.add(header.fragment);
|
||||
mFragments.clear();
|
||||
Iterator<Header> headerIterator = target.iterator();
|
||||
while (headerIterator.hasNext()) {
|
||||
Header header = headerIterator.next();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Workaround for bug in the AppCompat support library
|
||||
header.iconRes = R.drawable.empty;
|
||||
}
|
||||
|
||||
if (header.titleRes == R.string.debug_title) {
|
||||
if (BrowserApp.isRelease()) {
|
||||
headerIterator.remove();
|
||||
} else {
|
||||
mFragments.add(header.fragment);
|
||||
}
|
||||
} else {
|
||||
mFragments.add(header.fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return fragments.contains(fragmentName);
|
||||
return mFragments.contains(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +80,7 @@ public class SettingsActivity extends ThemableSettingsActivity {
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
PermissionsManager.getInstance().notifyPermissionsChange(permissions);
|
||||
PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults);
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
package acr.browser.lightning.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.BookmarkPage;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.constant.HistoryPage;
|
||||
import acr.browser.lightning.constant.StartPage;
|
||||
import acr.browser.lightning.database.BookmarkManager;
|
||||
import acr.browser.lightning.database.HistoryDatabase;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.OnSubscribe;
|
||||
import acr.browser.lightning.react.Schedulers;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
import acr.browser.lightning.utils.FileUtils;
|
||||
import acr.browser.lightning.utils.UrlUtils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
/**
|
||||
* A manager singleton that holds all the {@link LightningView}
|
||||
* and tracks the current tab. It handles creation, deletion,
|
||||
* restoration, state saving, and switching of tabs.
|
||||
*/
|
||||
public class TabsManager {
|
||||
|
||||
private static final String TAG = TabsManager.class.getSimpleName();
|
||||
private static final String BUNDLE_KEY = "WEBVIEW_";
|
||||
private static final String URL_KEY = "URL_KEY";
|
||||
private static final String BUNDLE_STORAGE = "SAVED_TABS.parcel";
|
||||
|
||||
private final List<LightningView> mTabList = new ArrayList<>(1);
|
||||
@Nullable private LightningView mCurrentTab;
|
||||
@Nullable private TabNumberChangedListener mTabNumberListener;
|
||||
|
||||
private boolean mIsInitialized = false;
|
||||
private final List<Runnable> mPostInitializationWorkList = new ArrayList<>();
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
@Inject HistoryDatabase mHistoryManager;
|
||||
@Inject Bus mEventBus;
|
||||
@Inject Application mApp;
|
||||
|
||||
public TabsManager() {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
// TODO remove and make presenter call new tab methods so it always knows
|
||||
@Deprecated
|
||||
public interface TabNumberChangedListener {
|
||||
void tabNumberChanged(int newNumber);
|
||||
}
|
||||
|
||||
public void setTabNumberChangedListener(@Nullable TabNumberChangedListener listener) {
|
||||
mTabNumberListener = listener;
|
||||
}
|
||||
|
||||
public void cancelPendingWork() {
|
||||
mPostInitializationWorkList.clear();
|
||||
}
|
||||
|
||||
public void doAfterInitialization(@NonNull Runnable runnable) {
|
||||
if (mIsInitialized) {
|
||||
runnable.run();
|
||||
} else {
|
||||
mPostInitializationWorkList.add(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishInitialization() {
|
||||
mIsInitialized = true;
|
||||
for (Runnable runnable : mPostInitializationWorkList) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores old tabs that were open before the browser
|
||||
* was closed. Handles the intent used to open the browser.
|
||||
*
|
||||
* @param activity the activity needed to create tabs.
|
||||
* @param intent the intent that started the browser activity.
|
||||
* @param incognito whether or not we are in incognito mode.
|
||||
*/
|
||||
public synchronized Observable<Void> initializeTabs(@NonNull final Activity activity,
|
||||
@Nullable final Intent intent,
|
||||
final boolean incognito) {
|
||||
return Observable.create(new Action<Void>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull final Subscriber<Void> subscriber) {
|
||||
|
||||
// Make sure we start with a clean tab list
|
||||
shutdown();
|
||||
|
||||
// If incognito, only create one tab, do not handle intent
|
||||
// in order to protect user privacy
|
||||
if (incognito) {
|
||||
newTab(activity, null, true);
|
||||
subscriber.onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
String url = null;
|
||||
if (intent != null) {
|
||||
url = intent.getDataString();
|
||||
}
|
||||
Log.d(TAG, "URL from intent: " + url);
|
||||
mCurrentTab = null;
|
||||
if (mPreferenceManager.getRestoreLostTabsEnabled()) {
|
||||
restoreLostTabs(url, activity, subscriber);
|
||||
} else {
|
||||
newTab(activity, null, false);
|
||||
finishInitialization();
|
||||
subscriber.onComplete();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void restoreLostTabs(@Nullable final String url, @NonNull final Activity activity,
|
||||
@NonNull final Subscriber subscriber) {
|
||||
|
||||
restoreState().subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.main()).subscribe(new OnSubscribe<Bundle>() {
|
||||
@Override
|
||||
public void onNext(Bundle item) {
|
||||
LightningView tab = newTab(activity, "", false);
|
||||
String url = item.getString(URL_KEY);
|
||||
if (url != null && tab.getWebView() != null) {
|
||||
if (UrlUtils.isBookmarkUrl(url)) {
|
||||
new BookmarkPage(tab, activity, mBookmarkManager).load();
|
||||
} else if (UrlUtils.isStartPageUrl(url)) {
|
||||
new StartPage(tab, mApp).load();
|
||||
} else if (UrlUtils.isHistoryUrl(url)) {
|
||||
new HistoryPage(tab, mApp, mHistoryManager).load();
|
||||
}
|
||||
} else if (tab.getWebView() != null) {
|
||||
tab.getWebView().restoreState(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (url != null) {
|
||||
if (url.startsWith(Constants.FILE)) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setCancelable(true)
|
||||
.setTitle(R.string.title_warning)
|
||||
.setMessage(R.string.message_blocked_local)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.action_open, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
newTab(activity, url, false);
|
||||
}
|
||||
}).show();
|
||||
} else {
|
||||
newTab(activity, url, false);
|
||||
}
|
||||
}
|
||||
if (mTabList.size() == 0) {
|
||||
newTab(activity, null, false);
|
||||
}
|
||||
finishInitialization();
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to resume all the tabs in the browser.
|
||||
* This is necessary because we cannot pause the
|
||||
* WebView when the app is open currently due to a
|
||||
* bug in the WebView, where calling onResume doesn't
|
||||
* consistently resume it.
|
||||
*
|
||||
* @param context the context needed to initialize
|
||||
* the LightningView preferences.
|
||||
*/
|
||||
public void resumeAll(@NonNull Context context) {
|
||||
LightningView current = getCurrentTab();
|
||||
if (current != null) {
|
||||
current.resumeTimers();
|
||||
}
|
||||
for (LightningView tab : mTabList) {
|
||||
if (tab != null) {
|
||||
tab.onResume();
|
||||
tab.initializePreferences(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to pause all the tabs in the browser.
|
||||
* This is necessary because we cannot pause the
|
||||
* WebView when the app is open currently due to a
|
||||
* bug in the WebView, where calling onResume doesn't
|
||||
* consistently resume it.
|
||||
*/
|
||||
public void pauseAll() {
|
||||
LightningView current = getCurrentTab();
|
||||
if (current != null) {
|
||||
current.pauseTimers();
|
||||
}
|
||||
for (LightningView tab : mTabList) {
|
||||
if (tab != null) {
|
||||
tab.onPause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tab at the given position in tabs list, or
|
||||
* null if position is not in tabs list range.
|
||||
*
|
||||
* @param position the index in tabs list
|
||||
* @return the corespondent {@link LightningView},
|
||||
* or null if the index is invalid
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized LightningView getTabAtPosition(final int position) {
|
||||
if (position < 0 || position >= mTabList.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mTabList.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees memory for each tab in the
|
||||
* manager. Note: this will only work
|
||||
* on API < KITKAT as on KITKAT onward
|
||||
* the WebViews manage their own
|
||||
* memory correctly.
|
||||
*/
|
||||
public synchronized void freeMemory() {
|
||||
for (LightningView tab : mTabList) {
|
||||
//noinspection deprecation
|
||||
tab.freeMemory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the manager. This destroys
|
||||
* all tabs and clears the references
|
||||
* to those tabs. Current tab is also
|
||||
* released for garbage collection.
|
||||
*/
|
||||
public synchronized void shutdown() {
|
||||
for (LightningView tab : mTabList) {
|
||||
tab.onDestroy();
|
||||
}
|
||||
mTabList.clear();
|
||||
mIsInitialized = false;
|
||||
mCurrentTab = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards network connection status to the WebViews.
|
||||
*
|
||||
* @param isConnected whether there is a network
|
||||
* connection or not.
|
||||
*/
|
||||
public synchronized void notifyConnectionStatus(final boolean isConnected) {
|
||||
for (LightningView tab : mTabList) {
|
||||
final WebView webView = tab.getWebView();
|
||||
if (webView != null) {
|
||||
webView.setNetworkAvailable(isConnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The current number of tabs in the manager.
|
||||
*
|
||||
* @return the number of tabs in the list.
|
||||
*/
|
||||
public synchronized int size() {
|
||||
return mTabList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* The index of the last tab in the manager.
|
||||
*
|
||||
* @return the last tab in the list or -1 if there are no tabs.
|
||||
*/
|
||||
public synchronized int last() {
|
||||
return mTabList.size() - 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The last tab in the tab manager.
|
||||
*
|
||||
* @return the last tab, or null if
|
||||
* there are no tabs.
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized LightningView lastTab() {
|
||||
if (last() < 0) {
|
||||
return null;
|
||||
}
|
||||
return mTabList.get(last());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new tab. The tab is
|
||||
* automatically added to the tabs list.
|
||||
*
|
||||
* @param activity the activity needed to create the tab.
|
||||
* @param url the URL to initialize the tab with.
|
||||
* @param isIncognito whether the tab is an incognito
|
||||
* tab or not.
|
||||
* @return a valid initialized tab.
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized LightningView newTab(@NonNull final Activity activity,
|
||||
@Nullable final String url,
|
||||
final boolean isIncognito) {
|
||||
Log.d(TAG, "New tab");
|
||||
final LightningView tab = new LightningView(activity, url, isIncognito);
|
||||
mTabList.add(tab);
|
||||
if (mTabNumberListener != null) {
|
||||
mTabNumberListener.tabNumberChanged(size());
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tab from the list and destroys the tab.
|
||||
* If the tab removed is the current tab, the reference
|
||||
* to the current tab will be nullified.
|
||||
*
|
||||
* @param position The position of the tab to remove.
|
||||
*/
|
||||
private synchronized void removeTab(final int position) {
|
||||
if (position >= mTabList.size()) {
|
||||
return;
|
||||
}
|
||||
final LightningView tab = mTabList.remove(position);
|
||||
if (mCurrentTab == tab) {
|
||||
mCurrentTab = null;
|
||||
}
|
||||
tab.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a tab from the manager. If the tab
|
||||
* being deleted is the current tab, this method
|
||||
* will switch the current tab to a new valid tab.
|
||||
*
|
||||
* @param position the position of the tab to delete.
|
||||
* @return returns true if the current tab
|
||||
* was deleted, false otherwise.
|
||||
*/
|
||||
public synchronized boolean deleteTab(int position) {
|
||||
Log.d(TAG, "Delete tab: " + position);
|
||||
final LightningView currentTab = getCurrentTab();
|
||||
int current = positionOf(currentTab);
|
||||
|
||||
if (current == position) {
|
||||
if (size() == 1) {
|
||||
mCurrentTab = null;
|
||||
} else if (current < size() - 1) {
|
||||
// There is another tab after this one
|
||||
switchToTab(current + 1);
|
||||
} else {
|
||||
switchToTab(current - 1);
|
||||
}
|
||||
}
|
||||
|
||||
removeTab(position);
|
||||
if (mTabNumberListener != null) {
|
||||
mTabNumberListener.tabNumberChanged(size());
|
||||
}
|
||||
return current == position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position of the given tab.
|
||||
*
|
||||
* @param tab the tab to look for.
|
||||
* @return the position of the tab or -1
|
||||
* if the tab is not in the list.
|
||||
*/
|
||||
public synchronized int positionOf(final LightningView tab) {
|
||||
return mTabList.indexOf(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the state of the current WebViews,
|
||||
* to a bundle which is then stored in persistent
|
||||
* storage and can be unparceled.
|
||||
*/
|
||||
public void saveState() {
|
||||
Bundle outState = new Bundle(ClassLoader.getSystemClassLoader());
|
||||
Log.d(Constants.TAG, "Saving tab state");
|
||||
for (int n = 0; n < mTabList.size(); n++) {
|
||||
LightningView tab = mTabList.get(n);
|
||||
Bundle state = new Bundle(ClassLoader.getSystemClassLoader());
|
||||
if (tab.getWebView() != null && !UrlUtils.isSpecialUrl(tab.getUrl())) {
|
||||
tab.getWebView().saveState(state);
|
||||
outState.putBundle(BUNDLE_KEY + n, state);
|
||||
} else if (tab.getWebView() != null) {
|
||||
state.putString(URL_KEY, tab.getUrl());
|
||||
outState.putBundle(BUNDLE_KEY + n, state);
|
||||
}
|
||||
}
|
||||
FileUtils.writeBundleToStorage(mApp, outState, BUNDLE_STORAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to clear the saved
|
||||
* state if you do not wish it to be
|
||||
* restored when the browser next starts.
|
||||
*/
|
||||
public void clearSavedState() {
|
||||
FileUtils.deleteBundleInStorage(mApp, BUNDLE_STORAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the previously saved tabs from the
|
||||
* bundle stored in peristent file storage.
|
||||
* It will create new tabs for each tab saved
|
||||
* and will delete the saved instance file when
|
||||
* restoration is complete.
|
||||
*/
|
||||
private Observable<Bundle> restoreState() {
|
||||
return Observable.create(new Action<Bundle>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<Bundle> subscriber) {
|
||||
Bundle savedState = FileUtils.readBundleFromStorage(mApp, BUNDLE_STORAGE);
|
||||
if (savedState != null) {
|
||||
Log.d(Constants.TAG, "Restoring previous WebView state now");
|
||||
for (String key : savedState.keySet()) {
|
||||
if (key.startsWith(BUNDLE_KEY)) {
|
||||
subscriber.onNext(savedState.getBundle(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
FileUtils.deleteBundleInStorage(mApp, BUNDLE_STORAGE);
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link WebView} associated to the current tab,
|
||||
* or null if there is no current tab.
|
||||
*
|
||||
* @return a {@link WebView} or null if there is no current tab.
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized WebView getCurrentWebView() {
|
||||
return mCurrentTab != null ? mCurrentTab.getWebView() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the current tab.
|
||||
*
|
||||
* @return Return the index of the current tab, or -1 if the
|
||||
* current tab is null.
|
||||
*/
|
||||
public int indexOfCurrentTab() {
|
||||
return mTabList.indexOf(mCurrentTab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the tab.
|
||||
*
|
||||
* @return Return the index of the tab, or -1 if the tab isn't in the list.
|
||||
*/
|
||||
public int indexOfTab(LightningView tab) {
|
||||
return mTabList.indexOf(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current {@link LightningView} or null if
|
||||
* no current tab has been set.
|
||||
*
|
||||
* @return a {@link LightningView} or null if there
|
||||
* is no current tab.
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized LightningView getCurrentTab() {
|
||||
return mCurrentTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the current tab to the one at the given position.
|
||||
* It returns the selected tab that has been switced to.
|
||||
*
|
||||
* @return the selected tab or null if position is out of tabs range.
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized LightningView switchToTab(final int position) {
|
||||
Log.d(TAG, "switch to tab: " + position);
|
||||
if (position < 0 || position >= mTabList.size()) {
|
||||
Log.e(TAG, "Returning a null LightningView requested for position: " + position);
|
||||
return null;
|
||||
} else {
|
||||
final LightningView tab = mTabList.get(position);
|
||||
if (tab != null) {
|
||||
mCurrentTab = tab;
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,20 +3,31 @@ package acr.browser.lightning.activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
|
||||
public abstract class ThemableBrowserActivity extends AppCompatActivity {
|
||||
|
||||
@Inject PreferenceManager mPreferences;
|
||||
|
||||
private int mTheme;
|
||||
private boolean mShowTabsInDrawer;
|
||||
private boolean mShouldRunOnResumeActions = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mTheme = PreferenceManager.getInstance().getUseTheme();
|
||||
mShowTabsInDrawer = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet());
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mTheme = mPreferences.getUseTheme();
|
||||
mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet());
|
||||
|
||||
// set the theme
|
||||
if (mTheme == 1) {
|
||||
@@ -27,23 +38,42 @@ public abstract class ThemableBrowserActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && mShouldRunOnResumeActions) {
|
||||
mShouldRunOnResumeActions = false;
|
||||
onWindowVisibleToUserAfterResume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the activity is resumed
|
||||
* and the UI becomes visible to the user.
|
||||
* Called by onWindowFocusChanged only if
|
||||
* onResume has been called.
|
||||
*/
|
||||
public void onWindowVisibleToUserAfterResume() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
int theme = PreferenceManager.getInstance().getUseTheme();
|
||||
boolean drawerTabs = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet());
|
||||
mShouldRunOnResumeActions = true;
|
||||
int theme = mPreferences.getUseTheme();
|
||||
boolean drawerTabs = mPreferences.getShowTabsInDrawer(!isTablet());
|
||||
if (theme != mTheme || mShowTabsInDrawer != drawerTabs) {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTablet() {
|
||||
boolean isTablet() {
|
||||
return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
Intent intent = getIntent();
|
||||
finish();
|
||||
startActivity(intent);
|
||||
startActivity(new Intent(this, getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package acr.browser.lightning.activity;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
|
||||
@@ -11,9 +14,12 @@ public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivi
|
||||
|
||||
private int mTheme;
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mTheme = PreferenceManager.getInstance().getUseTheme();
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mTheme = mPreferenceManager.getUseTheme();
|
||||
|
||||
// set the theme
|
||||
if (mTheme == 0) {
|
||||
@@ -32,7 +38,7 @@ public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivi
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (PreferenceManager.getInstance().getUseTheme() != mTheme) {
|
||||
if (mPreferenceManager.getUseTheme() != mTheme) {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package acr.browser.lightning.app;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.activity.BrowserActivity;
|
||||
import acr.browser.lightning.activity.ReadingActivity;
|
||||
import acr.browser.lightning.activity.TabsManager;
|
||||
import acr.browser.lightning.activity.ThemableBrowserActivity;
|
||||
import acr.browser.lightning.activity.ThemableSettingsActivity;
|
||||
import acr.browser.lightning.browser.BrowserPresenter;
|
||||
import acr.browser.lightning.constant.StartPage;
|
||||
import acr.browser.lightning.dialog.LightningDialogBuilder;
|
||||
import acr.browser.lightning.download.LightningDownloadListener;
|
||||
import acr.browser.lightning.fragment.BookmarkSettingsFragment;
|
||||
import acr.browser.lightning.fragment.BookmarksFragment;
|
||||
import acr.browser.lightning.fragment.DebugSettingsFragment;
|
||||
import acr.browser.lightning.fragment.LightningPreferenceFragment;
|
||||
import acr.browser.lightning.fragment.PrivacySettingsFragment;
|
||||
import acr.browser.lightning.fragment.TabsFragment;
|
||||
import acr.browser.lightning.search.SuggestionsAdapter;
|
||||
import acr.browser.lightning.utils.AdBlock;
|
||||
import acr.browser.lightning.utils.ProxyUtils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
import acr.browser.lightning.view.LightningWebClient;
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {AppModule.class})
|
||||
public interface AppComponent {
|
||||
|
||||
void inject(BrowserActivity activity);
|
||||
|
||||
void inject(BookmarksFragment fragment);
|
||||
|
||||
void inject(BookmarkSettingsFragment fragment);
|
||||
|
||||
void inject(SuggestionsAdapter adapter);
|
||||
|
||||
void inject(LightningDialogBuilder builder);
|
||||
|
||||
void inject(TabsFragment fragment);
|
||||
|
||||
void inject(LightningView lightningView);
|
||||
|
||||
void inject(ThemableBrowserActivity activity);
|
||||
|
||||
void inject(LightningPreferenceFragment fragment);
|
||||
|
||||
void inject(BrowserApp app);
|
||||
|
||||
void inject(ProxyUtils proxyUtils);
|
||||
|
||||
void inject(ReadingActivity activity);
|
||||
|
||||
void inject(LightningWebClient webClient);
|
||||
|
||||
void inject(ThemableSettingsActivity activity);
|
||||
|
||||
void inject(AdBlock adBlock);
|
||||
|
||||
void inject(LightningDownloadListener listener);
|
||||
|
||||
void inject(PrivacySettingsFragment fragment);
|
||||
|
||||
void inject(StartPage startPage);
|
||||
|
||||
void inject(BrowserPresenter presenter);
|
||||
|
||||
void inject(TabsManager manager);
|
||||
|
||||
void inject(DebugSettingsFragment fragment);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package acr.browser.lightning.app;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import net.i2p.android.ui.I2PAndroidHelper;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class AppModule {
|
||||
private final BrowserApp mApp;
|
||||
@NonNull private final Bus mBus;
|
||||
|
||||
public AppModule(BrowserApp app) {
|
||||
this.mApp = app;
|
||||
this.mBus = new Bus();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Application provideApplication() {
|
||||
return mApp;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Context provideContext() {
|
||||
return mApp.getApplicationContext();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Provides
|
||||
public Bus provideBus() {
|
||||
return mBus;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Provides
|
||||
@Singleton
|
||||
public I2PAndroidHelper provideI2PAndroidHelper() {
|
||||
return new I2PAndroidHelper(mApp.getApplicationContext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package acr.browser.lightning.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.BuildConfig;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.MemoryLeakUtils;
|
||||
|
||||
public class BrowserApp extends Application {
|
||||
|
||||
private static final String TAG = BrowserApp.class.getSimpleName();
|
||||
|
||||
private static AppComponent mAppComponent;
|
||||
private static final Executor mIOThread = Executors.newSingleThreadExecutor();
|
||||
private static final Executor mTaskThread = Executors.newCachedThreadPool();
|
||||
|
||||
@Inject Bus mBus;
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
|
||||
mAppComponent.inject(this);
|
||||
|
||||
if (mPreferenceManager.getUseLeakCanary() && !isRelease()) {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
if (!isRelease() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
|
||||
registerActivityLifecycleCallbacks(new MemoryLeakUtils.LifecycleAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
Log.d(TAG, "Cleaning up after the Android framework");
|
||||
MemoryLeakUtils.clearNextServedView(BrowserApp.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static BrowserApp get(@NonNull Context context) {
|
||||
return (BrowserApp) context.getApplicationContext();
|
||||
}
|
||||
|
||||
public static AppComponent getAppComponent() {
|
||||
return mAppComponent;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Executor getIOThread() {
|
||||
return mIOThread;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Executor getTaskThread() {
|
||||
return mTaskThread;
|
||||
}
|
||||
|
||||
public static Bus getBus(@NonNull Context context) {
|
||||
return get(context).mBus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this is a release build.
|
||||
*
|
||||
* @return true if this is a release build, false otherwise.
|
||||
*/
|
||||
public static boolean isRelease() {
|
||||
return !BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.toLowerCase().equals("release");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package acr.browser.lightning.async;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* Created 9/27/2015 Anthony Restaino
|
||||
*/
|
||||
public class AsyncExecutor implements Executor {
|
||||
|
||||
private static final String TAG = AsyncExecutor.class.getSimpleName();
|
||||
private static final AsyncExecutor INSTANCE = new AsyncExecutor();
|
||||
private final Queue<Runnable> mQueue = new ArrayDeque<>(1);
|
||||
private final ExecutorService mExecutor = Executors.newFixedThreadPool(4);
|
||||
|
||||
private AsyncExecutor() {}
|
||||
|
||||
@NonNull
|
||||
public static AsyncExecutor getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public synchronized void notifyThreadFinish() {
|
||||
if (mQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Runnable runnable = mQueue.remove();
|
||||
execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
mExecutor.shutdownNow();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NonNull Runnable command) {
|
||||
try {
|
||||
mExecutor.execute(command);
|
||||
} catch (RejectedExecutionException ignored) {
|
||||
mQueue.add(command);
|
||||
Log.d(TAG, "Thread was enqueued");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package acr.browser.lightning.async;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
|
||||
private static final String TAG = ImageDownloadTask.class.getSimpleName();
|
||||
@NonNull private final WeakReference<ImageView> mFaviconImage;
|
||||
@NonNull private final WeakReference<Context> mContextReference;
|
||||
@NonNull private final HistoryItem mWeb;
|
||||
private final String mUrl;
|
||||
@NonNull private final Bitmap mDefaultBitmap;
|
||||
|
||||
public ImageDownloadTask(@NonNull ImageView bmImage,
|
||||
@NonNull HistoryItem web,
|
||||
@NonNull Bitmap defaultBitmap,
|
||||
@NonNull Context context) {
|
||||
// Set a tag on the ImageView so we know if the view
|
||||
// has gone out of scope and should not be used
|
||||
bmImage.setTag(web.getUrl().hashCode());
|
||||
this.mFaviconImage = new WeakReference<>(bmImage);
|
||||
this.mWeb = web;
|
||||
this.mUrl = web.getUrl();
|
||||
this.mDefaultBitmap = defaultBitmap;
|
||||
this.mContextReference = new WeakReference<>(context.getApplicationContext());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bitmap doInBackground(Void... params) {
|
||||
Bitmap mIcon = null;
|
||||
// unique path for each url that is bookmarked.
|
||||
if (mUrl == null) {
|
||||
return mDefaultBitmap;
|
||||
}
|
||||
Context context = mContextReference.get();
|
||||
if (context == null) {
|
||||
return mDefaultBitmap;
|
||||
}
|
||||
File cache = context.getCacheDir();
|
||||
final Uri uri = Uri.parse(mUrl);
|
||||
if (uri.getHost() == null || uri.getScheme() == null) {
|
||||
return mDefaultBitmap;
|
||||
}
|
||||
final String hash = String.valueOf(uri.getHost().hashCode());
|
||||
final File image = new File(cache, hash + ".png");
|
||||
final String urlDisplay = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico";
|
||||
// checks to see if the image exists
|
||||
if (!image.exists()) {
|
||||
FileOutputStream fos = null;
|
||||
InputStream in = null;
|
||||
try {
|
||||
// if not, download it...
|
||||
final URL urlDownload = new URL(urlDisplay);
|
||||
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setConnectTimeout(1000);
|
||||
connection.setReadTimeout(1000);
|
||||
connection.connect();
|
||||
in = connection.getInputStream();
|
||||
|
||||
if (in != null) {
|
||||
mIcon = BitmapFactory.decodeStream(in);
|
||||
}
|
||||
// ...and cache it
|
||||
if (mIcon != null) {
|
||||
fos = new FileOutputStream(image);
|
||||
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
Log.d(Constants.TAG, "Downloaded: " + urlDisplay);
|
||||
}
|
||||
|
||||
} catch (Exception ignored) {
|
||||
Log.d(TAG, "Could not download: " + urlDisplay);
|
||||
} finally {
|
||||
Utils.close(in);
|
||||
Utils.close(fos);
|
||||
}
|
||||
} else {
|
||||
// if it exists, retrieve it from the cache
|
||||
mIcon = BitmapFactory.decodeFile(image.getPath());
|
||||
}
|
||||
if (mIcon == null) {
|
||||
InputStream in = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
// if not, download it...
|
||||
final URL urlDownload = new URL("https://www.google.com/s2/favicons?domain_url=" + uri.toString());
|
||||
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setConnectTimeout(1000);
|
||||
connection.setReadTimeout(1000);
|
||||
connection.connect();
|
||||
in = connection.getInputStream();
|
||||
|
||||
if (in != null) {
|
||||
mIcon = BitmapFactory.decodeStream(in);
|
||||
}
|
||||
// ...and cache it
|
||||
if (mIcon != null) {
|
||||
fos = new FileOutputStream(image);
|
||||
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Could not download Google favicon");
|
||||
} finally {
|
||||
Utils.close(in);
|
||||
Utils.close(fos);
|
||||
}
|
||||
}
|
||||
if (mIcon == null) {
|
||||
return mDefaultBitmap;
|
||||
} else {
|
||||
return mIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
super.onPostExecute(bitmap);
|
||||
AsyncExecutor.getInstance().notifyThreadFinish();
|
||||
final Bitmap fav = Utils.padFavicon(bitmap);
|
||||
final ImageView view = mFaviconImage.get();
|
||||
if (view != null && view.getTag().equals(mWeb.getUrl().hashCode())) {
|
||||
Context context = view.getContext();
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
view.setImageBitmap(fav);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
view.setImageBitmap(fav);
|
||||
}
|
||||
}
|
||||
mWeb.setBitmap(fav);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package acr.browser.lightning.browser;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.TabsManager;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.controller.UIController;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.react.OnSubscribe;
|
||||
import acr.browser.lightning.utils.UrlUtils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
/**
|
||||
* Presenter in charge of keeping track of
|
||||
* the current tab and setting the current tab
|
||||
* of the
|
||||
*/
|
||||
public class BrowserPresenter {
|
||||
|
||||
private static final String TAG = BrowserPresenter.class.getSimpleName();
|
||||
|
||||
@NonNull private final TabsManager mTabsModel;
|
||||
@Inject PreferenceManager mPreferences;
|
||||
@Inject Bus mEventBus;
|
||||
|
||||
@NonNull private final BrowserView mView;
|
||||
@Nullable private LightningView mCurrentTab;
|
||||
|
||||
private final boolean mIsIncognito;
|
||||
private boolean mShouldClose;
|
||||
|
||||
public BrowserPresenter(@NonNull BrowserView view, boolean isIncognito) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mTabsModel = ((UIController) view).getTabModel();
|
||||
mView = view;
|
||||
mIsIncognito = isIncognito;
|
||||
mTabsModel.setTabNumberChangedListener(new TabsManager.TabNumberChangedListener() {
|
||||
@Override
|
||||
public void tabNumberChanged(int newNumber) {
|
||||
mView.updateTabNumber(newNumber);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the tab manager with the new intent
|
||||
* that is handed in by the BrowserActivity.
|
||||
*
|
||||
* @param intent the intent to handle, may be null.
|
||||
*/
|
||||
public void setupTabs(@Nullable Intent intent) {
|
||||
mTabsModel.initializeTabs((Activity) mView, intent, mIsIncognito)
|
||||
.subscribe(new OnSubscribe<Void>() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
// At this point we always have at least a tab in the tab manager
|
||||
tabChanged(mTabsModel.last());
|
||||
mView.updateTabNumber(mTabsModel.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the presenter that a change occurred to
|
||||
* the current tab. Currently doesn't do anything
|
||||
* other than tell the view to notify the adapter
|
||||
* about the change.
|
||||
*
|
||||
* @param tab the tab that changed, may be null.
|
||||
*/
|
||||
public void tabChangeOccurred(@Nullable LightningView tab) {
|
||||
mView.notifyTabViewChanged(mTabsModel.indexOfTab(tab));
|
||||
}
|
||||
|
||||
private void onTabChanged(@Nullable LightningView newTab) {
|
||||
Log.d(TAG, "On tab changed");
|
||||
if (newTab == null) {
|
||||
mView.removeTabView();
|
||||
if (mCurrentTab != null) {
|
||||
mCurrentTab.pauseTimers();
|
||||
mCurrentTab.onDestroy();
|
||||
}
|
||||
} else {
|
||||
if (newTab.getWebView() == null) {
|
||||
mView.removeTabView();
|
||||
if (mCurrentTab != null) {
|
||||
mCurrentTab.pauseTimers();
|
||||
mCurrentTab.onDestroy();
|
||||
}
|
||||
} else {
|
||||
if (mCurrentTab != null) {
|
||||
// TODO: Restore this when Google fixes the bug where the WebView is
|
||||
// blank after calling onPause followed by onResume.
|
||||
// mCurrentTab.onPause();
|
||||
mCurrentTab.setForegroundTab(false);
|
||||
}
|
||||
|
||||
newTab.resumeTimers();
|
||||
newTab.onResume();
|
||||
newTab.setForegroundTab(true);
|
||||
|
||||
mView.updateProgress(newTab.getProgress());
|
||||
mView.setBackButtonEnabled(newTab.canGoBack());
|
||||
mView.setForwardButtonEnabled(newTab.canGoForward());
|
||||
mView.updateUrl(newTab.getUrl(), true);
|
||||
mView.setTabView(newTab.getWebView());
|
||||
int index = mTabsModel.indexOfTab(newTab);
|
||||
if (index >= 0) {
|
||||
mView.notifyTabViewChanged(mTabsModel.indexOfTab(newTab));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentTab = newTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all tabs but the current tab.
|
||||
*/
|
||||
public void closeAllOtherTabs() {
|
||||
|
||||
while (mTabsModel.last() != mTabsModel.indexOfCurrentTab()) {
|
||||
deleteTab(mTabsModel.last());
|
||||
}
|
||||
|
||||
while (0 != mTabsModel.indexOfCurrentTab()) {
|
||||
deleteTab(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the tab at the specified position.
|
||||
*
|
||||
* @param position the position at which to
|
||||
* delete the tab.
|
||||
*/
|
||||
public void deleteTab(int position) {
|
||||
Log.d(TAG, "delete Tab");
|
||||
final LightningView tabToDelete = mTabsModel.getTabAtPosition(position);
|
||||
|
||||
if (tabToDelete == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UrlUtils.isSpecialUrl(tabToDelete.getUrl()) && !mIsIncognito) {
|
||||
mPreferences.setSavedUrl(tabToDelete.getUrl());
|
||||
}
|
||||
|
||||
final boolean isShown = tabToDelete.isShown();
|
||||
boolean shouldClose = mShouldClose && isShown && Boolean.TRUE.equals(tabToDelete.getTag());
|
||||
final LightningView currentTab = mTabsModel.getCurrentTab();
|
||||
if (mTabsModel.size() == 1 && currentTab != null &&
|
||||
(UrlUtils.isSpecialUrl(currentTab.getUrl()) ||
|
||||
currentTab.getUrl().equals(mPreferences.getHomepage()))) {
|
||||
mView.closeActivity();
|
||||
return;
|
||||
} else {
|
||||
if (isShown) {
|
||||
mView.removeTabView();
|
||||
}
|
||||
boolean currentDeleted = mTabsModel.deleteTab(position);
|
||||
if (currentDeleted) {
|
||||
tabChanged(mTabsModel.indexOfCurrentTab());
|
||||
}
|
||||
}
|
||||
|
||||
final LightningView afterTab = mTabsModel.getCurrentTab();
|
||||
mView.notifyTabViewRemoved(position);
|
||||
|
||||
if (afterTab == null) {
|
||||
mView.closeBrowser();
|
||||
return;
|
||||
} else if (afterTab != currentTab) {
|
||||
//TODO remove this?
|
||||
// switchTabs(currentTab, afterTab);
|
||||
// if (currentTab != null) {
|
||||
// currentTab.pauseTimers();
|
||||
// }
|
||||
mView.notifyTabViewChanged(mTabsModel.indexOfCurrentTab());
|
||||
}
|
||||
|
||||
if (shouldClose) {
|
||||
mShouldClose = false;
|
||||
mView.closeActivity();
|
||||
}
|
||||
|
||||
mView.updateTabNumber(mTabsModel.size());
|
||||
|
||||
Log.d(TAG, "deleted tab");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a new intent from the the main
|
||||
* BrowserActivity.
|
||||
*
|
||||
* @param intent the intent to handle,
|
||||
* may be null.
|
||||
*/
|
||||
public void onNewIntent(@Nullable final Intent intent) {
|
||||
mTabsModel.doAfterInitialization(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final String url;
|
||||
if (intent != null) {
|
||||
url = intent.getDataString();
|
||||
} else {
|
||||
url = null;
|
||||
}
|
||||
int num = 0;
|
||||
if (intent != null && intent.getExtras() != null) {
|
||||
num = intent.getExtras().getInt(Constants.INTENT_ORIGIN);
|
||||
}
|
||||
|
||||
if (num == 1) {
|
||||
loadUrlInCurrentView(url);
|
||||
} else if (url != null) {
|
||||
if (url.startsWith(Constants.FILE)) {
|
||||
mView.showBlockedLocalFileDialog(new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
newTab(url, true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newTab(url, true);
|
||||
}
|
||||
mShouldClose = true;
|
||||
LightningView tab = mTabsModel.lastTab();
|
||||
if (tab != null) {
|
||||
tab.setTag(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a URL in the current tab.
|
||||
*
|
||||
* @param url the URL to load, must
|
||||
* not be null.
|
||||
*/
|
||||
public void loadUrlInCurrentView(@NonNull final String url) {
|
||||
final LightningView currentTab = mTabsModel.getCurrentTab();
|
||||
if (currentTab == null) {
|
||||
// This is a problem, probably an assert will be better than a return
|
||||
return;
|
||||
}
|
||||
|
||||
currentTab.loadUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the presenter that it should
|
||||
* shut down. This should be called when
|
||||
* the BrowserActivity is destroyed so that
|
||||
* we don't leak any memory.
|
||||
*/
|
||||
public void shutdown() {
|
||||
onTabChanged(null);
|
||||
mTabsModel.setTabNumberChangedListener(null);
|
||||
mTabsModel.cancelPendingWork();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the presenter that we wish
|
||||
* to switch to a different tab at the
|
||||
* specified position. If the position
|
||||
* is not in the model, this method will
|
||||
* do nothing.
|
||||
*
|
||||
* @param position the position of the
|
||||
* tab to switch to.
|
||||
*/
|
||||
public synchronized void tabChanged(int position) {
|
||||
Log.d(TAG, "tabChanged: " + position);
|
||||
if (position < 0 || position >= mTabsModel.size()) {
|
||||
return;
|
||||
}
|
||||
LightningView tab = mTabsModel.switchToTab(position);
|
||||
onTabChanged(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new tab with the specified URL. You
|
||||
* can choose to show the tab or load it in the
|
||||
* background.
|
||||
*
|
||||
* @param url the URL to load, may be null if you
|
||||
* don't wish to load anything.
|
||||
* @param show whether or not to switch to this
|
||||
* tab after opening it.
|
||||
* @return true if we successfully created the tab,
|
||||
* false if we have hit max tabs.
|
||||
*/
|
||||
public synchronized boolean newTab(@Nullable String url, boolean show) {
|
||||
// Limit number of tabs for limited version of app
|
||||
if (!Constants.FULL_VERSION && mTabsModel.size() >= 10) {
|
||||
mView.showSnackbar(R.string.max_tabs);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.d(TAG, "New tab, show: " + show);
|
||||
|
||||
LightningView startingTab = mTabsModel.newTab((Activity) mView, url, mIsIncognito);
|
||||
if (mTabsModel.size() == 1) {
|
||||
startingTab.resumeTimers();
|
||||
}
|
||||
|
||||
mView.notifyTabViewAdded();
|
||||
|
||||
if (show) {
|
||||
LightningView tab = mTabsModel.switchToTab(mTabsModel.last());
|
||||
onTabChanged(tab);
|
||||
}
|
||||
|
||||
mView.updateTabNumber(mTabsModel.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package acr.browser.lightning.browser;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.view.View;
|
||||
|
||||
public interface BrowserView {
|
||||
|
||||
void setTabView(@NonNull View view);
|
||||
|
||||
void removeTabView();
|
||||
|
||||
void updateUrl(String url, boolean shortUrl);
|
||||
|
||||
void updateProgress(int progress);
|
||||
|
||||
void updateTabNumber(int number);
|
||||
|
||||
void closeBrowser();
|
||||
|
||||
void closeActivity();
|
||||
|
||||
void showBlockedLocalFileDialog(DialogInterface.OnClickListener listener);
|
||||
|
||||
void showSnackbar(@StringRes int resource);
|
||||
|
||||
void setForwardButtonEnabled(boolean enabled);
|
||||
|
||||
void setBackButtonEnabled(boolean enabled);
|
||||
|
||||
void notifyTabViewRemoved(int position);
|
||||
|
||||
void notifyTabViewAdded();
|
||||
|
||||
void notifyTabViewChanged(int position);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package acr.browser.lightning.browser;
|
||||
|
||||
public interface TabsView {
|
||||
|
||||
void tabAdded();
|
||||
|
||||
void tabRemoved(int position);
|
||||
|
||||
void tabChanged(int position);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package acr.browser.lightning.bus;
|
||||
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
|
||||
public final class BookmarkEvents {
|
||||
|
||||
private BookmarkEvents() {
|
||||
// No instances
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to delete the selected bookmark
|
||||
*/
|
||||
public static class Deleted {
|
||||
public final HistoryItem item;
|
||||
|
||||
public Deleted(final HistoryItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to add/del a bookmark to the currently displayed page
|
||||
*/
|
||||
public static class ToggleBookmarkForCurrentPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended by the {@link acr.browser.lightning.fragment.BookmarksFragment} when it wants to close
|
||||
* itself (generally in reply to a {@link acr.browser.lightning.bus.BrowserEvents.UserPressedBack}
|
||||
* event.
|
||||
*/
|
||||
public static class CloseBookmarks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended when a bookmark is edited
|
||||
*/
|
||||
public static class BookmarkChanged {
|
||||
|
||||
public final HistoryItem oldBookmark;
|
||||
public final HistoryItem newBookmark;
|
||||
|
||||
public BookmarkChanged(final HistoryItem oldItem, final HistoryItem newItem) {
|
||||
oldBookmark = oldItem;
|
||||
newBookmark = newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package acr.browser.lightning.bus;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
public final class BrowserEvents {
|
||||
|
||||
private BrowserEvents() {
|
||||
// No instances
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link acr.browser.lightning.activity.BrowserActivity} signal a new bookmark was added
|
||||
* (mainly to the {@link acr.browser.lightning.fragment.BookmarksFragment}).
|
||||
*/
|
||||
public static class BookmarkAdded {
|
||||
public final String title, url;
|
||||
|
||||
public BookmarkAdded(final String title, final String url) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the current page has a new url. This is generally used to update the
|
||||
* {@link acr.browser.lightning.fragment.BookmarksFragment} interface.
|
||||
*/
|
||||
public static class CurrentPageUrl {
|
||||
public final String url;
|
||||
|
||||
public CurrentPageUrl(final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the BookmarksFragment and TabsFragment that the user pressed the back button
|
||||
*/
|
||||
public static class UserPressedBack {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notify the Browser to display a SnackBar in the main activity
|
||||
*/
|
||||
public static class ShowSnackBarMessage {
|
||||
@Nullable public final String message;
|
||||
@StringRes
|
||||
public final int stringRes;
|
||||
|
||||
public ShowSnackBarMessage(@Nullable final String message) {
|
||||
this.message = message;
|
||||
this.stringRes = -1;
|
||||
}
|
||||
|
||||
public ShowSnackBarMessage(@StringRes final int stringRes) {
|
||||
this.message = null;
|
||||
this.stringRes = stringRes;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class OpenHistoryInCurrentTab {
|
||||
}
|
||||
|
||||
/**
|
||||
* The user want to open the given url in the current tab
|
||||
*/
|
||||
public final static class OpenUrlInCurrentTab {
|
||||
public final String url;
|
||||
|
||||
public OpenUrlInCurrentTab(final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to open the given url as new tab
|
||||
*/
|
||||
public final static class OpenUrlInNewTab {
|
||||
public final String url;
|
||||
|
||||
public OpenUrlInNewTab(final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package acr.browser.lightning.bus;
|
||||
|
||||
/**
|
||||
* Collections of navigation events, like go back or go forward
|
||||
*
|
||||
* @author Stefano Pacifici
|
||||
* @date 2015/09/15
|
||||
*/
|
||||
public class NavigationEvents {
|
||||
private NavigationEvents() {
|
||||
// No instances please
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses back
|
||||
* button.
|
||||
*/
|
||||
public static class GoBack {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses forward
|
||||
* button.
|
||||
*/
|
||||
public static class GoForward {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses the home
|
||||
* button.
|
||||
*/
|
||||
public static class GoHome {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package acr.browser.lightning.bus;
|
||||
|
||||
/**
|
||||
* A collection of events been sent by {@link acr.browser.lightning.fragment.TabsFragment}
|
||||
*
|
||||
* @author Stefano Pacifici
|
||||
* @date 2015/09/14
|
||||
*/
|
||||
public final class TabEvents {
|
||||
|
||||
private TabEvents() {
|
||||
// No instances
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the
|
||||
* tab exit button
|
||||
*/
|
||||
public static class CloseTab {
|
||||
public final int position;
|
||||
|
||||
public CloseTab(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the
|
||||
* tab itself.
|
||||
*/
|
||||
public static class ShowTab {
|
||||
public final int position;
|
||||
|
||||
public ShowTab(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long press on the
|
||||
* tab itself.
|
||||
*/
|
||||
public static class ShowCloseDialog {
|
||||
public final int position;
|
||||
|
||||
public ShowCloseDialog(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user want to create a
|
||||
* new tab.
|
||||
*/
|
||||
public static class NewTab {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long presses on
|
||||
* new tab button.
|
||||
*/
|
||||
public static class NewTabLongPress {
|
||||
}
|
||||
}
|
||||
@@ -4,88 +4,148 @@
|
||||
package acr.browser.lightning.constant;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.BrowserApp;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.database.BookmarkManager;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public class BookmarkPage {
|
||||
public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
/**
|
||||
* The bookmark page standard suffix
|
||||
*/
|
||||
public static final String FILENAME = "bookmarks.html";
|
||||
|
||||
public static final String HEADING = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" +
|
||||
private static final String HEADING_1 = "<!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" +
|
||||
"<meta name=viewport content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>\n" +
|
||||
"<title>";
|
||||
|
||||
private static final String HEADING_2 = "</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='";
|
||||
private static final String PART1 = "<div class=box><a href='";
|
||||
|
||||
public static final String PART2 = "'></a>\n" +
|
||||
private 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=";
|
||||
private static final String PART3 = "https://www.google.com/s2/favicons?domain=";
|
||||
|
||||
public static final String PART4 = "' />";
|
||||
private static final String PART4 = "' />";
|
||||
|
||||
public static final String PART5 = "</p></div></div></div>";
|
||||
private static final String PART5 = "</p></div></div></div>";
|
||||
|
||||
public static final String END = "</div></body></html>";
|
||||
private 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);
|
||||
private File mFilesDir;
|
||||
private File mCacheDir;
|
||||
|
||||
private final Application mApp;
|
||||
private final BookmarkManager mManager;
|
||||
@NonNull private final WeakReference<LightningView> mTabReference;
|
||||
private final Bitmap mFolderIcon;
|
||||
@NonNull private final String mTitle;
|
||||
|
||||
public BookmarkPage(LightningView tab, @NonNull Activity activity, BookmarkManager manager) {
|
||||
mApp = BrowserApp.get(activity);
|
||||
final Bitmap folderIcon = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_folder, false);
|
||||
mTitle = mApp.getString(R.string.action_bookmarks);
|
||||
mManager = manager;
|
||||
mTabReference = new WeakReference<>(tab);
|
||||
mFolderIcon = folderIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mCacheDir = mApp.getCacheDir();
|
||||
mFilesDir = mApp.getFilesDir();
|
||||
cacheDefaultFolderIcon();
|
||||
buildBookmarkPage(null, mManager);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
LightningView tab = mTabReference.get();
|
||||
if (tab != null) {
|
||||
File bookmarkWebPage = new File(mFilesDir, FILENAME);
|
||||
tab.loadUrl(Constants.FILE + bookmarkWebPage);
|
||||
}
|
||||
final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING);
|
||||
}
|
||||
|
||||
String folderIconPath = Constants.FILE + activity.getCacheDir() + "/folder.png";
|
||||
for (int n = 0; n < list.size(); n++) {
|
||||
private void cacheDefaultFolderIcon() {
|
||||
FileOutputStream outputStream = null;
|
||||
File image = new File(mCacheDir, "folder.png");
|
||||
try {
|
||||
outputStream = new FileOutputStream(image);
|
||||
mFolderIcon.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
|
||||
mFolderIcon.recycle();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildBookmarkPage(@Nullable final String folder, @NonNull final BookmarkManager manager) {
|
||||
final List<HistoryItem> list = manager.getBookmarksCopyFromFolder(folder, true);
|
||||
final File bookmarkWebPage;
|
||||
if (folder == null || folder.isEmpty()) {
|
||||
bookmarkWebPage = new File(mFilesDir, FILENAME);
|
||||
} else {
|
||||
bookmarkWebPage = new File(mFilesDir, folder + '-' + FILENAME);
|
||||
}
|
||||
final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
|
||||
|
||||
final String folderIconPath = Constants.FILE + mCacheDir + "/folder.png";
|
||||
for (int n = 0, size = list.size(); n < size; n++) {
|
||||
final HistoryItem item = list.get(n);
|
||||
bookmarkBuilder.append(BookmarkPage.PART1);
|
||||
bookmarkBuilder.append(PART1);
|
||||
if (item.isFolder()) {
|
||||
File folderPage = new File(activity.getFilesDir(), item.getTitle() + '-' + BookmarkPage.FILENAME);
|
||||
final File folderPage = new File(mFilesDir, item.getTitle() + '-' + FILENAME);
|
||||
bookmarkBuilder.append(Constants.FILE).append(folderPage);
|
||||
bookmarkBuilder.append(BookmarkPage.PART2);
|
||||
bookmarkBuilder.append(PART2);
|
||||
bookmarkBuilder.append(folderIconPath);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
buildBookmarkPage(activity, item.getTitle(), manager.getBookmarksFromFolder(item.getTitle(), true));
|
||||
}
|
||||
}).run();
|
||||
buildBookmarkPage(item.getTitle(), manager);
|
||||
} else {
|
||||
bookmarkBuilder.append(item.getUrl());
|
||||
bookmarkBuilder.append(BookmarkPage.PART2).append(BookmarkPage.PART3);
|
||||
bookmarkBuilder.append(PART2).append(PART3);
|
||||
bookmarkBuilder.append(item.getUrl());
|
||||
}
|
||||
bookmarkBuilder.append(BookmarkPage.PART4);
|
||||
bookmarkBuilder.append(PART4);
|
||||
bookmarkBuilder.append(item.getTitle());
|
||||
bookmarkBuilder.append(BookmarkPage.PART5);
|
||||
bookmarkBuilder.append(PART5);
|
||||
}
|
||||
bookmarkBuilder.append(BookmarkPage.END);
|
||||
bookmarkBuilder.append(END);
|
||||
FileWriter bookWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookWriter = new FileWriter(bookmarkWebPage, false);
|
||||
bookWriter.write(bookmarkBuilder.toString());
|
||||
} catch (IOException e) {
|
||||
@@ -95,4 +155,8 @@ public class BookmarkPage {
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
executeOnExecutor(BrowserApp.getIOThread());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
*/
|
||||
package acr.browser.lightning.constant;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import acr.browser.lightning.BuildConfig;
|
||||
|
||||
public final class Constants {
|
||||
@@ -27,10 +25,24 @@ public final class Constants {
|
||||
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 JAVASCRIPT_THEME_COLOR = "(function () {\n" +
|
||||
" \"use strict\";\n" +
|
||||
" var metas, i, tag;\n" +
|
||||
" metas = document.getElementsByTagName('meta');\n" +
|
||||
" if (metas !== null) {\n" +
|
||||
" for (i = 0; i < metas.length; i++) {\n" +
|
||||
" tag = metas[i].getAttribute('name');\n" +
|
||||
" if (tag !== null && tag.toLowerCase() === 'theme-color') {\n" +
|
||||
" return metas[i].getAttribute('content');\n" +
|
||||
" }\n" +
|
||||
" console.log(tag);\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
'\n' +
|
||||
" return '';\n" +
|
||||
"}());";
|
||||
|
||||
public static final String LOAD_READING_URL = "ReadingUrl";
|
||||
|
||||
@@ -50,4 +62,6 @@ public final class Constants {
|
||||
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"};
|
||||
|
||||
public static final String INTENT_ORIGIN = "URL_INTENT_ORIGIN";
|
||||
}
|
||||
|
||||
@@ -3,27 +3,32 @@
|
||||
*/
|
||||
package acr.browser.lightning.constant;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
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.app.BrowserApp;
|
||||
import acr.browser.lightning.database.HistoryDatabase;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public class HistoryPage {
|
||||
public class HistoryPage extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
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 HEADING_1 = "<!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>";
|
||||
|
||||
private static final String HEADING_2 = "</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=\"";
|
||||
|
||||
@@ -35,26 +40,58 @@ public class HistoryPage {
|
||||
|
||||
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);
|
||||
@NonNull private final WeakReference<LightningView> mTabReference;
|
||||
@NonNull private final Application mApp;
|
||||
@NonNull private final String mTitle;
|
||||
private final HistoryDatabase mHistoryDatabase;
|
||||
|
||||
@Nullable private String mHistoryUrl = null;
|
||||
|
||||
public HistoryPage(LightningView tab, @NonNull Application app, HistoryDatabase database) {
|
||||
mTabReference = new WeakReference<>(tab);
|
||||
mApp = app;
|
||||
mTitle = app.getString(R.string.action_history);
|
||||
mHistoryDatabase = database;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mHistoryUrl = getHistoryPage();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
LightningView tab = mTabReference.get();
|
||||
if (tab != null && mHistoryUrl != null) {
|
||||
tab.loadUrl(mHistoryUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getHistoryPage() {
|
||||
StringBuilder historyBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
|
||||
List<HistoryItem> historyList = mHistoryDatabase.getLastHundredItems();
|
||||
Iterator<HistoryItem> it = historyList.iterator();
|
||||
HistoryItem helper;
|
||||
while (it.hasNext()) {
|
||||
helper = it.next();
|
||||
historyBuilder.append(HistoryPage.PART1);
|
||||
historyBuilder.append(PART1);
|
||||
historyBuilder.append(helper.getUrl());
|
||||
historyBuilder.append(HistoryPage.PART2);
|
||||
historyBuilder.append(PART2);
|
||||
historyBuilder.append(helper.getTitle());
|
||||
historyBuilder.append(HistoryPage.PART3);
|
||||
historyBuilder.append(PART3);
|
||||
historyBuilder.append(helper.getUrl());
|
||||
historyBuilder.append(HistoryPage.PART4);
|
||||
historyBuilder.append(PART4);
|
||||
}
|
||||
|
||||
historyBuilder.append(HistoryPage.END);
|
||||
File historyWebPage = new File(context.getFilesDir(), FILENAME);
|
||||
historyBuilder.append(END);
|
||||
File historyWebPage = new File(mApp.getFilesDir(), FILENAME);
|
||||
FileWriter historyWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
historyWriter = new FileWriter(historyWebPage, false);
|
||||
historyWriter.write(historyBuilder.toString());
|
||||
} catch (IOException e) {
|
||||
@@ -65,9 +102,22 @@ public class HistoryPage {
|
||||
return Constants.FILE + historyWebPage;
|
||||
}
|
||||
|
||||
private static List<HistoryItem> getWebHistory(Context context) {
|
||||
HistoryDatabase databaseHandler = HistoryDatabase.getInstance(context
|
||||
.getApplicationContext());
|
||||
return databaseHandler.getLastHundredItems();
|
||||
public void load() {
|
||||
executeOnExecutor(BrowserApp.getIOThread());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to immediately delete the history
|
||||
* page on the current thread. This will clear the
|
||||
* cached history page that was stored on file.
|
||||
*
|
||||
* @param application the application object needed to get the file.
|
||||
*/
|
||||
public static void deleteHistoryPage(@NonNull Application application) {
|
||||
File historyWebPage = new File(application.getFilesDir(), FILENAME);
|
||||
if (historyWebPage.exists()) {
|
||||
historyWebPage.delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,29 +3,36 @@
|
||||
*/
|
||||
package acr.browser.lightning.constant;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.activity.BrowserApp;
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public class StartPage {
|
||||
public class StartPage extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
public static final String FILENAME = "homepage.html";
|
||||
|
||||
public static final String HEAD = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\">"
|
||||
private static final String HEAD_1 = "<!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>"
|
||||
+ "<title>";
|
||||
|
||||
private static final String HEAD_2 = "</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;}"
|
||||
@@ -41,11 +48,42 @@ public class StartPage {
|
||||
+ "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\">"
|
||||
private static final String MIDDLE = "\" ></br></br><form onsubmit=\"return search()\" class=\"search_bar\" autocomplete=\"off\">"
|
||||
+ "<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>";
|
||||
private static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}</script></body></html>";
|
||||
|
||||
@NonNull private final String mTitle;
|
||||
@NonNull private final Application mApp;
|
||||
@NonNull private final WeakReference<LightningView> mTabReference;
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
private String mStartpageUrl;
|
||||
|
||||
public StartPage(LightningView tab, @NonNull Application app) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mTitle = app.getString(R.string.home);
|
||||
mApp = app;
|
||||
mTabReference = new WeakReference<>(tab);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mStartpageUrl = getHomepage();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
LightningView tab = mTabReference.get();
|
||||
if (tab != null) {
|
||||
tab.loadUrl(mStartpageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method builds the homepage and returns the local URL to be loaded
|
||||
@@ -53,15 +91,16 @@ public class StartPage {
|
||||
*
|
||||
* @return the URL to load
|
||||
*/
|
||||
public static String getHomepage(Activity activity) {
|
||||
StringBuilder homepageBuilder = new StringBuilder(StartPage.HEAD);
|
||||
@NonNull
|
||||
private String getHomepage() {
|
||||
StringBuilder homepageBuilder = new StringBuilder(HEAD_1 + mTitle + HEAD_2);
|
||||
String icon;
|
||||
String searchUrl;
|
||||
switch (PreferenceManager.getInstance().getSearchChoice()) {
|
||||
switch (mPreferenceManager.getSearchChoice()) {
|
||||
case 0:
|
||||
// CUSTOM SEARCH
|
||||
icon = "file:///android_asset/lightning.png";
|
||||
searchUrl = PreferenceManager.getInstance().getSearchUrl();
|
||||
searchUrl = mPreferenceManager.getSearchUrl();
|
||||
break;
|
||||
case 1:
|
||||
// GOOGLE_SEARCH;
|
||||
@@ -88,14 +127,14 @@ public class StartPage {
|
||||
break;
|
||||
case 5:
|
||||
// STARTPAGE_SEARCH;
|
||||
icon = "file:///android_asset/startpage.png";
|
||||
// "https://startpage.com/graphics/startp_logo.gif";
|
||||
icon = "file:///android_asset/png";
|
||||
// "https://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";
|
||||
icon = "file:///android_asset/png";
|
||||
// "https://com/graphics/startp_logo.gif";
|
||||
searchUrl = Constants.STARTPAGE_MOBILE_SEARCH;
|
||||
break;
|
||||
case 7:
|
||||
@@ -131,13 +170,14 @@ public class StartPage {
|
||||
}
|
||||
|
||||
homepageBuilder.append(icon);
|
||||
homepageBuilder.append(StartPage.MIDDLE);
|
||||
homepageBuilder.append(MIDDLE);
|
||||
homepageBuilder.append(searchUrl);
|
||||
homepageBuilder.append(StartPage.END);
|
||||
homepageBuilder.append(END);
|
||||
|
||||
File homepage = new File(activity.getFilesDir(), StartPage.FILENAME);
|
||||
File homepage = new File(mApp.getFilesDir(), FILENAME);
|
||||
FileWriter hWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
hWriter = new FileWriter(homepage, false);
|
||||
hWriter.write(homepageBuilder.toString());
|
||||
} catch (IOException e) {
|
||||
@@ -148,4 +188,9 @@ public class StartPage {
|
||||
|
||||
return Constants.FILE + homepage;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
executeOnExecutor(BrowserApp.getIOThread());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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,67 @@
|
||||
/*
|
||||
* Copyright 2014 A.C.R. Development
|
||||
*/
|
||||
package acr.browser.lightning.controller;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
import acr.browser.lightning.activity.TabsManager;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public interface UIController {
|
||||
|
||||
void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable Drawable drawable);
|
||||
|
||||
@ColorInt
|
||||
int getUiColor();
|
||||
|
||||
boolean getUseDarkTheme();
|
||||
|
||||
void updateUrl(@Nullable String title, boolean shortUrl);
|
||||
|
||||
void updateProgress(int n);
|
||||
|
||||
void updateHistory(@Nullable String title, @NonNull String url);
|
||||
|
||||
void openFileChooser(ValueCallback<Uri> uploadMsg);
|
||||
|
||||
void onShowCustomView(View view, CustomViewCallback callback);
|
||||
|
||||
void onShowCustomView(View view, CustomViewCallback callback, int requestedOrienation);
|
||||
|
||||
void onHideCustomView();
|
||||
|
||||
void onCreateWindow(Message resultMsg);
|
||||
|
||||
void onCloseWindow(LightningView view);
|
||||
|
||||
void hideActionBar();
|
||||
|
||||
void showActionBar();
|
||||
|
||||
void showFileChooser(ValueCallback<Uri[]> filePathCallback);
|
||||
|
||||
void closeEmptyTab();
|
||||
|
||||
void showCloseDialog(int position);
|
||||
|
||||
void newTabClicked();
|
||||
|
||||
void setForwardButtonEnabled(boolean enabled);
|
||||
|
||||
void setBackButtonEnabled(boolean enabled);
|
||||
|
||||
void tabChanged(LightningView tab);
|
||||
|
||||
TabsManager getTabModel();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package acr.browser.lightning.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
public class BookmarkLocalSync {
|
||||
|
||||
private static final String TAG = BookmarkLocalSync.class.getSimpleName();
|
||||
|
||||
private static final String STOCK_BOOKMARKS_CONTENT = "content://browser/bookmarks";
|
||||
private static final String CHROME_BOOKMARKS_CONTENT = "content://com.android.chrome.browser/bookmarks";
|
||||
private static final String CHROME_BETA_BOOKMARKS_CONTENT = "content://com.chrome.beta.browser/bookmarks";
|
||||
private static final String CHROME_DEV_BOOKMARKS_CONTENT = "content://com.chrome.dev.browser/bookmarks";
|
||||
|
||||
private static final String COLUMN_TITLE = "title";
|
||||
private static final String COLUMN_URL = "url";
|
||||
private static final String COLUMN_BOOKMARK = "bookmark";
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
|
||||
public enum Source {
|
||||
STOCK,
|
||||
CHROME_STABLE,
|
||||
CHROME_BETA,
|
||||
CHROME_DEV
|
||||
}
|
||||
|
||||
public BookmarkLocalSync(@NonNull Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public List<HistoryItem> getBookmarksFromContentUri(String contentUri) {
|
||||
List<HistoryItem> list = new ArrayList<>();
|
||||
Cursor cursor = getBrowserCursor(contentUri);
|
||||
try {
|
||||
if (cursor != null) {
|
||||
for (int n = 0; n < cursor.getColumnCount(); n++) {
|
||||
Log.d(TAG, cursor.getColumnName(n));
|
||||
}
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
if (cursor.getInt(2) == 1) {
|
||||
String url = cursor.getString(0);
|
||||
String title = cursor.getString(1);
|
||||
if (url.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (title == null || title.isEmpty()) {
|
||||
title = Utils.getDomainName(url);
|
||||
}
|
||||
if (title != null) {
|
||||
list.add(new HistoryItem(url, title));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Utils.close(cursor);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getBrowserCursor(String contentUri) {
|
||||
Cursor cursor;
|
||||
Uri uri = Uri.parse(contentUri);
|
||||
try {
|
||||
cursor = mContext.getContentResolver().query(uri,
|
||||
new String[]{COLUMN_URL, COLUMN_TITLE, COLUMN_BOOKMARK}, null, null, null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Observable<List<Source>> getSupportedBrowsers() {
|
||||
return Observable.create(new Action<List<Source>>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<List<Source>> subscriber) {
|
||||
List<Source> sources = new ArrayList<>(1);
|
||||
if (isBrowserSupported(STOCK_BOOKMARKS_CONTENT)) {
|
||||
sources.add(Source.STOCK);
|
||||
}
|
||||
if (isBrowserSupported(CHROME_BOOKMARKS_CONTENT)) {
|
||||
sources.add(Source.CHROME_STABLE);
|
||||
}
|
||||
if (isBrowserSupported(CHROME_BETA_BOOKMARKS_CONTENT)) {
|
||||
sources.add(Source.CHROME_BETA);
|
||||
}
|
||||
if (isBrowserSupported(CHROME_DEV_BOOKMARKS_CONTENT)) {
|
||||
sources.add(Source.CHROME_DEV);
|
||||
}
|
||||
subscriber.onNext(sources);
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isBrowserSupported(String contentUri) {
|
||||
Cursor cursor = getBrowserCursor(contentUri);
|
||||
boolean supported = cursor != null;
|
||||
Utils.close(cursor);
|
||||
return supported;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@WorkerThread
|
||||
public List<HistoryItem> getBookmarksFromStockBrowser() {
|
||||
return getBookmarksFromContentUri(STOCK_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@WorkerThread
|
||||
public List<HistoryItem> getBookmarksFromChrome() {
|
||||
return getBookmarksFromContentUri(CHROME_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@WorkerThread
|
||||
public List<HistoryItem> getBookmarksFromChromeBeta() {
|
||||
return getBookmarksFromContentUri(CHROME_BETA_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@WorkerThread
|
||||
public List<HistoryItem> getBookmarksFromChromeDev() {
|
||||
return getBookmarksFromContentUri(CHROME_DEV_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public boolean isBrowserImportSupported() {
|
||||
Cursor chrome = getChromeCursor();
|
||||
Utils.close(chrome);
|
||||
Cursor dev = getChromeDevCursor();
|
||||
Utils.close(dev);
|
||||
Cursor beta = getChromeBetaCursor();
|
||||
Cursor stock = getStockCursor();
|
||||
Utils.close(stock);
|
||||
return chrome != null || dev != null || beta != null || stock != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getChromeBetaCursor() {
|
||||
return getBrowserCursor(CHROME_BETA_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getChromeDevCursor() {
|
||||
return getBrowserCursor(CHROME_DEV_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getChromeCursor() {
|
||||
return getBrowserCursor(CHROME_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getStockCursor() {
|
||||
return getBrowserCursor(STOCK_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
public void printAllColumns() {
|
||||
printColumns(CHROME_BETA_BOOKMARKS_CONTENT);
|
||||
printColumns(CHROME_BOOKMARKS_CONTENT);
|
||||
printColumns(CHROME_DEV_BOOKMARKS_CONTENT);
|
||||
printColumns(STOCK_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
public void printColumns(String contentProvider) {
|
||||
Cursor cursor = null;
|
||||
Log.e(TAG, contentProvider);
|
||||
Uri uri = Uri.parse(contentProvider);
|
||||
try {
|
||||
cursor = mContext.getContentResolver().query(uri, null, null, null, null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error Occurred", e);
|
||||
}
|
||||
if (cursor != null) {
|
||||
for (int n = 0; n < cursor.getColumnCount(); n++) {
|
||||
Log.d(TAG, cursor.getColumnName(n));
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,10 +2,10 @@ 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 android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -13,82 +13,194 @@ import org.json.JSONObject;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
@Singleton
|
||||
public class BookmarkManager {
|
||||
|
||||
private final Context mContext;
|
||||
private static final String TAG = BookmarkManager.class.getSimpleName();
|
||||
|
||||
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;
|
||||
}
|
||||
@NonNull private final String DEFAULT_BOOKMARK_TITLE;
|
||||
|
||||
private BookmarkManager(Context context) {
|
||||
mContext = context;
|
||||
mBookmarkList.clear();
|
||||
mBookmarkList.addAll(getAllBookmarks(true));
|
||||
mBookmarkSearchSet = getBookmarkUrls(mBookmarkList);
|
||||
}
|
||||
private Map<String, HistoryItem> mBookmarksMap;
|
||||
@NonNull private String mCurrentFolder = "";
|
||||
@NonNull private final ExecutorService mExecutor;
|
||||
private File mFilesDir;
|
||||
|
||||
public boolean isBookmark(String url) {
|
||||
return mBookmarkSearchSet.contains(url);
|
||||
@Inject
|
||||
public BookmarkManager(@NonNull Context context) {
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled);
|
||||
mExecutor.execute(new BookmarkInitializer(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds the the HistoryItem item to permanent bookmark storage.
|
||||
* It returns true if the operation was successful.
|
||||
* Look for bookmark using the url
|
||||
*
|
||||
* @param url the lookup url
|
||||
* @return the bookmark as an {@link HistoryItem} or null
|
||||
*/
|
||||
@Nullable
|
||||
public HistoryItem findBookmarkForUrl(final String url) {
|
||||
return mBookmarksMap.get(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the BookmarkManager, it's a one-time operation and will be executed asynchronously.
|
||||
* When done, mReady flag will been set to true.
|
||||
*/
|
||||
private class BookmarkInitializer implements Runnable {
|
||||
private final Context mContext;
|
||||
|
||||
public BookmarkInitializer(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (BookmarkManager.this) {
|
||||
mFilesDir = mContext.getFilesDir();
|
||||
final Map<String, HistoryItem> bookmarks = new HashMap<>();
|
||||
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
|
||||
|
||||
BufferedReader bookmarksReader = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
if (bookmarksFile.exists() && bookmarksFile.isFile()) {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
inputStream = new FileInputStream(bookmarksFile);
|
||||
} else {
|
||||
inputStream = mContext.getResources().openRawResource(R.raw.default_bookmarks);
|
||||
}
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookmarksReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while ((line = bookmarksReader.readLine()) != null) {
|
||||
try {
|
||||
JSONObject object = new JSONObject(line);
|
||||
HistoryItem item = new HistoryItem();
|
||||
item.setTitle(object.getString(TITLE));
|
||||
final String url = object.getString(URL);
|
||||
item.setUrl(url);
|
||||
item.setFolder(object.getString(FOLDER));
|
||||
item.setOrder(object.getInt(ORDER));
|
||||
item.setImageId(R.drawable.ic_bookmark);
|
||||
bookmarks.put(url, item);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Can't parse line " + line, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading the bookmarks file", e);
|
||||
} finally {
|
||||
Utils.close(bookmarksReader);
|
||||
Utils.close(inputStream);
|
||||
}
|
||||
mBookmarksMap = bookmarks;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all the given bookmarks to the bookmark file using a temporary file
|
||||
*/
|
||||
private class BookmarksWriter implements Runnable {
|
||||
|
||||
private final List<HistoryItem> mBookmarks;
|
||||
|
||||
public BookmarksWriter(List<HistoryItem> bookmarks) {
|
||||
mBookmarks = bookmarks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final File tempFile = new File(mFilesDir,
|
||||
String.format(Locale.US, "bm_%d.dat", System.currentTimeMillis()));
|
||||
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
|
||||
boolean success = false;
|
||||
BufferedWriter bookmarkWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookmarkWriter = new BufferedWriter(new FileWriter(tempFile, false));
|
||||
JSONObject object = new JSONObject();
|
||||
for (HistoryItem item : mBookmarks) {
|
||||
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();
|
||||
}
|
||||
success = true;
|
||||
} catch (@NonNull IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(bookmarkWriter);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
// Overwrite the bookmarks file by renaming the temp file
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
tempFile.renameTo(bookmarksFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
mExecutor.shutdownNow();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public boolean isBookmark(String url) {
|
||||
return mBookmarksMap.containsKey(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds the the HistoryItem item to permanent bookmark storage.<br>
|
||||
* This operation is blocking if the manager is still not ready.
|
||||
*
|
||||
* @param item the item to add
|
||||
* @return It returns true if the operation was successful.
|
||||
*/
|
||||
public synchronized boolean addBookmark(HistoryItem item) {
|
||||
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
|
||||
if (item.getUrl() == null || mBookmarkSearchSet.contains(item.getUrl())) {
|
||||
public synchronized boolean addBookmark(@NonNull HistoryItem item) {
|
||||
final String url = item.getUrl();
|
||||
if (mBookmarksMap.containsKey(url)) {
|
||||
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);
|
||||
}
|
||||
mBookmarksMap.put(url, item);
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -97,29 +209,17 @@ public class BookmarkManager {
|
||||
*
|
||||
* @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);
|
||||
public synchronized void addBookmarkList(@Nullable List<HistoryItem> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (HistoryItem item : list) {
|
||||
final String url = item.getUrl();
|
||||
if (!mBookmarksMap.containsKey(url)) {
|
||||
mBookmarksMap.put(url, item);
|
||||
}
|
||||
}
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,13 +228,12 @@ public class BookmarkManager {
|
||||
*
|
||||
* @param deleteItem the bookmark item to delete
|
||||
*/
|
||||
public synchronized boolean deleteBookmark(HistoryItem deleteItem) {
|
||||
public synchronized boolean deleteBookmark(@Nullable HistoryItem deleteItem) {
|
||||
if (deleteItem == null || deleteItem.isFolder()) {
|
||||
return false;
|
||||
}
|
||||
mBookmarkSearchSet.remove(deleteItem.getUrl());
|
||||
mBookmarkList.remove(deleteItem);
|
||||
overwriteBookmarks(mBookmarkList);
|
||||
mBookmarksMap.remove(deleteItem.getUrl());
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -148,15 +247,15 @@ public class BookmarkManager {
|
||||
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);
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
if (item.getFolder().equals(oldName)) {
|
||||
item.setFolder(newName);
|
||||
} else if (item.isFolder() && item.getTitle().equals(oldName)) {
|
||||
item.setTitle(newName);
|
||||
item.setUrl(Constants.FOLDER + newName);
|
||||
}
|
||||
}
|
||||
overwriteBookmarks(mBookmarkList);
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,16 +264,32 @@ public class BookmarkManager {
|
||||
* @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();
|
||||
final Map<String, HistoryItem> bookmarks = new HashMap<>();
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
final String url = item.getUrl();
|
||||
if (item.isFolder()) {
|
||||
if (!item.getTitle().equals(name)) {
|
||||
bookmarks.put(url, item);
|
||||
}
|
||||
} else {
|
||||
if (item.getFolder().equals(name)) {
|
||||
item.setFolder("");
|
||||
}
|
||||
bookmarks.put(url, item);
|
||||
}
|
||||
}
|
||||
overwriteBookmarks(mBookmarkList);
|
||||
mBookmarksMap = bookmarks;
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method deletes ALL bookmarks created
|
||||
* by the user. Use this method carefully and
|
||||
* do not use it without explicit user consent.
|
||||
*/
|
||||
public synchronized void deleteAllBookmarks() {
|
||||
mBookmarksMap = new HashMap<>();
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,32 +298,32 @@ public class BookmarkManager {
|
||||
* @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) {
|
||||
public synchronized void editBookmark(@Nullable HistoryItem oldItem, @Nullable 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));
|
||||
newItem.setTitle(DEFAULT_BOOKMARK_TITLE);
|
||||
}
|
||||
overwriteBookmarks(mBookmarkList);
|
||||
final String oldUrl = oldItem.getUrl();
|
||||
final String newUrl = newItem.getUrl();
|
||||
if (!oldUrl.equals(newUrl)) {
|
||||
// The url has been changed, remove the old one
|
||||
mBookmarksMap.remove(oldUrl);
|
||||
}
|
||||
mBookmarksMap.put(newUrl, newItem);
|
||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exports the stored bookmarks to a text file in the device's
|
||||
* external download directory
|
||||
*/
|
||||
public synchronized void exportBookmarks(Activity activity) {
|
||||
public synchronized void exportBookmarks(@NonNull Activity activity) {
|
||||
List<HistoryItem> bookmarkList = getAllBookmarks(true);
|
||||
File bookmarksExport = new File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
@@ -222,6 +337,7 @@ public class BookmarkManager {
|
||||
}
|
||||
BufferedWriter bookmarkWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksExport,
|
||||
false));
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -235,7 +351,7 @@ public class BookmarkManager {
|
||||
}
|
||||
Utils.showSnackbar(activity, activity.getString(R.string.bookmark_export_path)
|
||||
+ ' ' + bookmarksExport.getPath());
|
||||
} catch (IOException | JSONException e) {
|
||||
} catch (@NonNull IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(bookmarkWriter);
|
||||
@@ -248,30 +364,12 @@ public class BookmarkManager {
|
||||
* This is a disk-bound operation and should not be
|
||||
* done very frequently.
|
||||
*
|
||||
* @param sort force to sort the returned bookmarkList
|
||||
* @return returns a list of bookmarks that can be sorted
|
||||
*/
|
||||
@NonNull
|
||||
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);
|
||||
}
|
||||
final List<HistoryItem> bookmarks = new ArrayList<>(mBookmarksMap.values());
|
||||
if (sort) {
|
||||
Collections.sort(bookmarks, new SortIgnoreCase());
|
||||
}
|
||||
@@ -287,16 +385,47 @@ public class BookmarkManager {
|
||||
* @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<>();
|
||||
@NonNull
|
||||
public synchronized List<HistoryItem> getBookmarksFromFolder(@Nullable String folder, boolean sort) {
|
||||
List<HistoryItem> bookmarks = new ArrayList<>(1);
|
||||
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));
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
if (item.getFolder().equals(folder))
|
||||
bookmarks.add(item);
|
||||
}
|
||||
if (sort) {
|
||||
Collections.sort(bookmarks, new SortIgnoreCase());
|
||||
}
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Different from {@link #getBookmarksFromFolder(String, boolean)} only in
|
||||
* that it doesn't affect the internal state of the bookmark manager which
|
||||
* tracks the current folder used by the bookmark drawer.
|
||||
* <p/>
|
||||
* 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
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized List<HistoryItem> getBookmarksCopyFromFolder(@Nullable String folder, boolean sort) {
|
||||
List<HistoryItem> bookmarks = new ArrayList<>(1);
|
||||
if (folder == null || folder.isEmpty()) {
|
||||
bookmarks.addAll(getFolders(sort));
|
||||
folder = "";
|
||||
}
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
if (item.getFolder().equals(folder))
|
||||
bookmarks.add(item);
|
||||
}
|
||||
if (sort) {
|
||||
Collections.sort(bookmarks, new SortIgnoreCase());
|
||||
@@ -314,17 +443,13 @@ public class BookmarkManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method is used internally for searching the bookmarks
|
||||
* Returns the current folder
|
||||
*
|
||||
* @return a sorted map of all bookmarks, useful for seeing if a bookmark exists
|
||||
* @return the current folder
|
||||
*/
|
||||
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;
|
||||
@Nullable
|
||||
public String getCurrentFolder() {
|
||||
return mCurrentFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,35 +459,25 @@ public class BookmarkManager {
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
@NonNull
|
||||
private synchronized List<HistoryItem> getFolders(boolean sort) {
|
||||
final HashMap<String, HistoryItem> folders = new HashMap<>();
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
final String folderName = item.getFolder();
|
||||
if (!folderName.isEmpty() && !folders.containsKey(folderName)) {
|
||||
final HistoryItem folder = new HistoryItem();
|
||||
folder.setIsFolder(true);
|
||||
folder.setTitle(folderName);
|
||||
folder.setImageId(R.drawable.ic_folder);
|
||||
folder.setUrl(Constants.FOLDER + folderName);
|
||||
folders.put(folderName, folder);
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(bookmarksReader);
|
||||
}
|
||||
final List<HistoryItem> result = new ArrayList<>(folders.values());
|
||||
if (sort) {
|
||||
Collections.sort(folders, new SortIgnoreCase());
|
||||
Collections.sort(result, new SortIgnoreCase());
|
||||
}
|
||||
return folders;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,28 +486,16 @@ public class BookmarkManager {
|
||||
*
|
||||
* @return a list of folder title strings
|
||||
*/
|
||||
@NonNull
|
||||
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);
|
||||
}
|
||||
final Set<String> folders = new HashSet<>();
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
final String folderName = item.getFolder();
|
||||
if (!folderName.isEmpty()) {
|
||||
folders.add(folderName);
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(bookmarksReader);
|
||||
}
|
||||
return folders;
|
||||
return new ArrayList<>(folders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,13 +504,14 @@ public class BookmarkManager {
|
||||
*
|
||||
* @param file the file to attempt to import bookmarks from
|
||||
*/
|
||||
public synchronized void importBookmarksFromFile(File file, Activity activity) {
|
||||
public synchronized void importBookmarksFromFile(@Nullable File file, @NonNull Activity activity) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
List<HistoryItem> list = new ArrayList<>();
|
||||
BufferedReader bookmarksReader = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookmarksReader = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
int number = 0;
|
||||
@@ -424,7 +528,7 @@ public class BookmarkManager {
|
||||
addBookmarkList(list);
|
||||
String message = activity.getResources().getString(R.string.message_import);
|
||||
Utils.showSnackbar(activity, number + " " + message);
|
||||
} catch (IOException | JSONException e) {
|
||||
} catch (@NonNull IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
Utils.createInformativeDialog(activity, R.string.title_error, R.string.import_bookmark_error);
|
||||
} finally {
|
||||
@@ -432,60 +536,13 @@ public class BookmarkManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
private static class SortIgnoreCase implements Comparator<HistoryItem> {
|
||||
|
||||
public int compare(HistoryItem o1, HistoryItem o2) {
|
||||
if (o1 == null || o2 == null || o1.getTitle() == null || o2.getTitle() == null) {
|
||||
public int compare(@Nullable HistoryItem o1, @Nullable HistoryItem o2) {
|
||||
if (o1 == null || o2 == null) {
|
||||
return 0;
|
||||
}
|
||||
if (o1.isFolder() == o2.isFolder()) {
|
||||
@@ -498,17 +555,4 @@ public class BookmarkManager {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,11 +8,19 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
|
||||
@Singleton
|
||||
public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
|
||||
// All Static variables
|
||||
@@ -20,7 +28,7 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
|
||||
// Database Name
|
||||
public static final String DATABASE_NAME = "historyManager";
|
||||
private static final String DATABASE_NAME = "historyManager";
|
||||
|
||||
// HistoryItems table name
|
||||
private static final String TABLE_HISTORY = "history";
|
||||
@@ -31,25 +39,28 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
private static final String KEY_TITLE = "title";
|
||||
private static final String KEY_TIME_VISITED = "time";
|
||||
|
||||
private SQLiteDatabase mDatabase;
|
||||
@Nullable private SQLiteDatabase mDatabase;
|
||||
|
||||
private static HistoryDatabase mInstance;
|
||||
|
||||
public static HistoryDatabase getInstance(Context context) {
|
||||
if (mInstance == null || mInstance.isClosed()) {
|
||||
mInstance = new HistoryDatabase(context);
|
||||
}
|
||||
return mInstance;
|
||||
@Inject
|
||||
public HistoryDatabase(@NonNull Context context) {
|
||||
super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION);
|
||||
initialize();
|
||||
}
|
||||
|
||||
private HistoryDatabase(Context context) {
|
||||
super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mDatabase = this.getWritableDatabase();
|
||||
private void initialize() {
|
||||
BrowserApp.getTaskThread().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (HistoryDatabase.this) {
|
||||
mDatabase = HistoryDatabase.this.getWritableDatabase();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Creating Tables
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
public void onCreate(@NonNull 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" + ')';
|
||||
@@ -58,50 +69,59 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
|
||||
// Upgrading database
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
public void onUpgrade(@NonNull 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() {
|
||||
public synchronized void deleteHistory() {
|
||||
mDatabase = openIfNecessary();
|
||||
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();
|
||||
mDatabase = null;
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
public synchronized void deleteHistoryItem(String url) {
|
||||
mDatabase.delete(TABLE_HISTORY, KEY_URL + " = ?", new String[] { url });
|
||||
@NonNull
|
||||
private SQLiteDatabase openIfNecessary() {
|
||||
if (mDatabase == null || !mDatabase.isOpen()) {
|
||||
mDatabase = this.getWritableDatabase();
|
||||
}
|
||||
return mDatabase;
|
||||
}
|
||||
|
||||
public synchronized void visitHistoryItem(String url, String title) {
|
||||
public synchronized void deleteHistoryItem(@NonNull String url) {
|
||||
mDatabase = openIfNecessary();
|
||||
mDatabase.delete(TABLE_HISTORY, KEY_URL + " = ?", new String[]{url});
|
||||
}
|
||||
|
||||
public synchronized void visitHistoryItem(@NonNull String url, @Nullable String title) {
|
||||
mDatabase = openIfNecessary();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_TITLE, title);
|
||||
values.put(KEY_TITLE, title == null ? "" : 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");
|
||||
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 });
|
||||
mDatabase.update(TABLE_HISTORY, values, KEY_URL + " = ?", new String[]{url});
|
||||
} else {
|
||||
addHistoryItem(new HistoryItem(url, title));
|
||||
addHistoryItem(new HistoryItem(url, title == null ? "" : title));
|
||||
}
|
||||
q.close();
|
||||
}
|
||||
|
||||
private synchronized void addHistoryItem(HistoryItem item) {
|
||||
private synchronized void addHistoryItem(@NonNull HistoryItem item) {
|
||||
mDatabase = openIfNecessary();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_URL, item.getUrl());
|
||||
values.put(KEY_TITLE, item.getTitle());
|
||||
@@ -109,9 +129,11 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
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);
|
||||
@Nullable
|
||||
synchronized String getHistoryItem(@NonNull String url) {
|
||||
mDatabase = openIfNecessary();
|
||||
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();
|
||||
@@ -122,8 +144,13 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
return m;
|
||||
}
|
||||
|
||||
public List<HistoryItem> findItemsContaining(String search) {
|
||||
List<HistoryItem> itemList = new ArrayList<>();
|
||||
@NonNull
|
||||
public synchronized List<HistoryItem> findItemsContaining(@Nullable String search) {
|
||||
mDatabase = openIfNecessary();
|
||||
List<HistoryItem> itemList = new ArrayList<>(5);
|
||||
if (search == null) {
|
||||
return itemList;
|
||||
}
|
||||
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " WHERE " + KEY_TITLE + " LIKE '%"
|
||||
+ search + "%' OR " + KEY_URL + " LIKE '%" + search + "%' " + "ORDER BY "
|
||||
+ KEY_TIME_VISITED + " DESC LIMIT 5";
|
||||
@@ -133,7 +160,6 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
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);
|
||||
@@ -145,8 +171,10 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
return itemList;
|
||||
}
|
||||
|
||||
public List<HistoryItem> getLastHundredItems() {
|
||||
List<HistoryItem> itemList = new ArrayList<>();
|
||||
@NonNull
|
||||
public synchronized List<HistoryItem> getLastHundredItems() {
|
||||
mDatabase = openIfNecessary();
|
||||
List<HistoryItem> itemList = new ArrayList<>(100);
|
||||
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED
|
||||
+ " DESC";
|
||||
|
||||
@@ -155,7 +183,6 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
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);
|
||||
@@ -167,7 +194,9 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
return itemList;
|
||||
}
|
||||
|
||||
public List<HistoryItem> getAllHistoryItems() {
|
||||
@NonNull
|
||||
public synchronized List<HistoryItem> getAllHistoryItems() {
|
||||
mDatabase = openIfNecessary();
|
||||
List<HistoryItem> itemList = new ArrayList<>();
|
||||
String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED
|
||||
+ " DESC";
|
||||
@@ -177,7 +206,6 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
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);
|
||||
@@ -188,22 +216,12 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
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() {
|
||||
public synchronized int getHistoryItemsCount() {
|
||||
mDatabase = openIfNecessary();
|
||||
String countQuery = "SELECT * FROM " + TABLE_HISTORY;
|
||||
Cursor cursor = mDatabase.rawQuery(countQuery, null);
|
||||
int n = cursor.getCount();
|
||||
cursor.close();
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,32 @@ package acr.browser.lightning.database;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import acr.browser.lightning.utils.Preconditions;
|
||||
|
||||
public class HistoryItem implements Comparable<HistoryItem> {
|
||||
|
||||
// private variables
|
||||
private int mId = 0;
|
||||
@NonNull
|
||||
private String mUrl = "";
|
||||
|
||||
@NonNull
|
||||
private String mTitle = "";
|
||||
|
||||
@NonNull
|
||||
private String mFolder = "";
|
||||
|
||||
@Nullable
|
||||
private Bitmap mBitmap = null;
|
||||
|
||||
private int mImageId = 0;
|
||||
private int mOrder = 0;
|
||||
private boolean mIsFolder = false;
|
||||
|
||||
// Empty constructor
|
||||
public HistoryItem() {
|
||||
public HistoryItem() {}
|
||||
|
||||
}
|
||||
|
||||
public HistoryItem(HistoryItem item) {
|
||||
public HistoryItem(@NonNull HistoryItem item) {
|
||||
this.mUrl = item.mUrl;
|
||||
this.mTitle = item.mTitle;
|
||||
this.mFolder = item.mFolder;
|
||||
@@ -31,43 +38,27 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
this.mIsFolder = item.mIsFolder;
|
||||
}
|
||||
|
||||
// constructor
|
||||
public HistoryItem(int id, String url, String title) {
|
||||
this.mId = id;
|
||||
public HistoryItem(@NonNull String url, @NonNull String title) {
|
||||
Preconditions.checkNonNull(url);
|
||||
Preconditions.checkNonNull(title);
|
||||
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) {
|
||||
public HistoryItem(@NonNull String url, @NonNull String title, int imageId) {
|
||||
Preconditions.checkNonNull(url);
|
||||
Preconditions.checkNonNull(title);
|
||||
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;
|
||||
}
|
||||
@@ -76,7 +67,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
mBitmap = image;
|
||||
}
|
||||
|
||||
public void setFolder(String folder) {
|
||||
public void setFolder(@Nullable String folder) {
|
||||
mFolder = (folder == null) ? "" : folder;
|
||||
}
|
||||
|
||||
@@ -88,31 +79,31 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
return mOrder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getFolder() {
|
||||
return mFolder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bitmap getBitmap() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
// getting name
|
||||
@NonNull
|
||||
public String getUrl() {
|
||||
return this.mUrl;
|
||||
}
|
||||
|
||||
// setting name
|
||||
public void setUrl(String url) {
|
||||
public void setUrl(@Nullable String url) {
|
||||
this.mUrl = (url == null) ? "" : url;
|
||||
}
|
||||
|
||||
// getting phone number
|
||||
@NonNull
|
||||
public String getTitle() {
|
||||
return this.mTitle;
|
||||
}
|
||||
|
||||
// setting phone number
|
||||
public void setTitle(String title) {
|
||||
public void setTitle(@Nullable String title) {
|
||||
this.mTitle = (title == null) ? "" : title;
|
||||
}
|
||||
|
||||
@@ -124,6 +115,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
return mIsFolder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTitle;
|
||||
@@ -131,40 +123,34 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull HistoryItem another) {
|
||||
return mTitle.compareTo(another.mTitle);
|
||||
int compare = this.mTitle.compareTo(another.mTitle);
|
||||
if (compare == 0) {
|
||||
return this.mUrl.compareTo(another.mUrl);
|
||||
}
|
||||
return compare;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(@Nullable Object object) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || ((Object) this).getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (this == object) return true;
|
||||
if (object == null) return false;
|
||||
if (!(object instanceof HistoryItem)) return false;
|
||||
|
||||
HistoryItem that = (HistoryItem) o;
|
||||
HistoryItem that = (HistoryItem) object;
|
||||
|
||||
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);
|
||||
return mImageId == that.mImageId &&
|
||||
this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl) &&
|
||||
this.mFolder.equals(that.mFolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = mId;
|
||||
result = 31 * result + mUrl.hashCode();
|
||||
int result = mUrl.hashCode();
|
||||
result = 31 * result + mImageId;
|
||||
result = 31 * result + mTitle.hashCode();
|
||||
result = 31 * result + (mBitmap != null ? mBitmap.hashCode() : 0);
|
||||
result = 32 * result + mFolder.hashCode();
|
||||
result = 31 * result + mImageId;
|
||||
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
package acr.browser.lightning.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.bus.BookmarkEvents;
|
||||
import acr.browser.lightning.bus.BrowserEvents;
|
||||
import acr.browser.lightning.constant.BookmarkPage;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
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.Utils;
|
||||
|
||||
/**
|
||||
* TODO Rename this class it doesn't build dialogs only for bookmarks
|
||||
* <p/>
|
||||
* Created by Stefano Pacifici on 02/09/15, based on Anthony C. Restaino's code.
|
||||
*/
|
||||
public class LightningDialogBuilder {
|
||||
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
@Inject HistoryDatabase mHistoryDatabase;
|
||||
@Inject Bus mEventBus;
|
||||
|
||||
@Inject
|
||||
public LightningDialogBuilder() {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the appropriated dialog for the long pressed link. It means that we try to understand
|
||||
* if the link is relative to a bookmark or is just a folder.
|
||||
*
|
||||
* @param context used to show the dialog
|
||||
* @param url the long pressed url
|
||||
*/
|
||||
public void showLongPressedDialogForBookmarkUrl(@NonNull final Context context, @NonNull final String url) {
|
||||
final HistoryItem item;
|
||||
if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) {
|
||||
// TODO hacky, make a better bookmark mechanism in the future
|
||||
final Uri uri = Uri.parse(url);
|
||||
final String filename = uri.getLastPathSegment();
|
||||
final String folderTitle = filename.substring(0, filename.length() - BookmarkPage.FILENAME.length() - 1);
|
||||
item = new HistoryItem();
|
||||
item.setIsFolder(true);
|
||||
item.setTitle(folderTitle);
|
||||
item.setImageId(R.drawable.ic_folder);
|
||||
item.setUrl(Constants.FOLDER + folderTitle);
|
||||
} else {
|
||||
item = mBookmarkManager.findBookmarkForUrl(url);
|
||||
}
|
||||
if (item != null) {
|
||||
if (item.isFolder()) {
|
||||
showBookmarkFolderLongPressedDialog(context, item);
|
||||
} else {
|
||||
showLongPressedDialogForBookmarkUrl(context, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showLongPressedDialogForBookmarkUrl(@NonNull final Context context, @NonNull final HistoryItem item) {
|
||||
final DialogInterface.OnClickListener dialogClickListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInNewTab(item.getUrl()));
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
if (mBookmarkManager.deleteBookmark(item)) {
|
||||
mEventBus.post(new BookmarkEvents.Deleted(item));
|
||||
}
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
showEditBookmarkDialog(context, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.action_bookmarks)
|
||||
.setMessage(R.string.dialog_bookmark)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
|
||||
.setNegativeButton(R.string.action_delete, dialogClickListener)
|
||||
.setNeutralButton(R.string.action_edit, dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showEditBookmarkDialog(@NonNull final Context context, @NonNull final HistoryItem item) {
|
||||
final AlertDialog.Builder editBookmarkDialog = new AlertDialog.Builder(context);
|
||||
editBookmarkDialog.setTitle(R.string.title_edit_bookmark);
|
||||
final View dialogLayout = View.inflate(context, R.layout.dialog_edit_bookmark, null);
|
||||
final EditText getTitle = (EditText) dialogLayout.findViewById(R.id.bookmark_title);
|
||||
getTitle.setText(item.getTitle());
|
||||
final EditText getUrl = (EditText) dialogLayout.findViewById(R.id.bookmark_url);
|
||||
getUrl.setText(item.getUrl());
|
||||
final AutoCompleteTextView getFolder =
|
||||
(AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder);
|
||||
getFolder.setHint(R.string.folder);
|
||||
getFolder.setText(item.getFolder());
|
||||
final List<String> folders = mBookmarkManager.getFolderTitles();
|
||||
final ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(context,
|
||||
android.R.layout.simple_dropdown_item_1line, folders);
|
||||
getFolder.setThreshold(1);
|
||||
getFolder.setAdapter(suggestionsAdapter);
|
||||
editBookmarkDialog.setView(dialogLayout);
|
||||
editBookmarkDialog.setPositiveButton(context.getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
HistoryItem editedItem = new HistoryItem();
|
||||
editedItem.setTitle(getTitle.getText().toString());
|
||||
editedItem.setUrl(getUrl.getText().toString());
|
||||
editedItem.setUrl(getUrl.getText().toString());
|
||||
editedItem.setFolder(getFolder.getText().toString());
|
||||
mBookmarkManager.editBookmark(item, editedItem);
|
||||
mEventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
|
||||
}
|
||||
});
|
||||
editBookmarkDialog.show();
|
||||
}
|
||||
|
||||
public void showBookmarkFolderLongPressedDialog(@NonNull final Context context, @NonNull final HistoryItem item) {
|
||||
// assert item.isFolder();
|
||||
final DialogInterface.OnClickListener dialogClickListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
showRenameFolderDialog(context, item);
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mBookmarkManager.deleteFolder(item.getTitle());
|
||||
// setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
mEventBus.post(new BookmarkEvents.Deleted(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.action_folder)
|
||||
.setMessage(R.string.dialog_folder)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.action_rename, dialogClickListener)
|
||||
.setNegativeButton(R.string.action_delete, dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showRenameFolderDialog(@NonNull final Context context, @NonNull final HistoryItem item) {
|
||||
// assert item.isFolder();
|
||||
final AlertDialog.Builder editFolderDialog = new AlertDialog.Builder(context);
|
||||
editFolderDialog.setTitle(R.string.title_rename_folder);
|
||||
final EditText getTitle = new EditText(context);
|
||||
getTitle.setHint(R.string.hint_title);
|
||||
getTitle.setText(item.getTitle());
|
||||
getTitle.setSingleLine();
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
int padding = Utils.dpToPx(10);
|
||||
layout.setPadding(padding, padding, padding, padding);
|
||||
layout.addView(getTitle);
|
||||
editFolderDialog.setView(layout);
|
||||
editFolderDialog.setPositiveButton(context.getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final String oldTitle = item.getTitle();
|
||||
final String newTitle = getTitle.getText().toString();
|
||||
final HistoryItem editedItem = new HistoryItem();
|
||||
editedItem.setTitle(newTitle);
|
||||
editedItem.setUrl(Constants.FOLDER + newTitle);
|
||||
editedItem.setFolder(item.getFolder());
|
||||
editedItem.setIsFolder(true);
|
||||
mBookmarkManager.renameFolder(oldTitle, newTitle);
|
||||
mEventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
|
||||
}
|
||||
});
|
||||
editFolderDialog.show();
|
||||
}
|
||||
|
||||
public void showLongPressedHistoryLinkDialog(final Context context, @NonNull final String url) {
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInNewTab(url));
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mHistoryDatabase.deleteHistoryItem(url);
|
||||
// openHistory();
|
||||
mEventBus.post(new BrowserEvents.OpenHistoryInCurrentTab());
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.action_history)
|
||||
.setMessage(R.string.dialog_history_long_press)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
|
||||
.setNegativeButton(R.string.action_delete, dialogClickListener)
|
||||
.setNeutralButton(R.string.action_open, dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
// TODO There should be a way in which we do not need an activity reference to dowload a file
|
||||
public void showLongPressImageDialog(@NonNull final Activity activity, @NonNull final String url,
|
||||
@NonNull final String userAgent) {
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInNewTab(url));
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url));
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
Utils.downloadFile(activity, mPreferenceManager, url, userAgent, "attachment");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(url.replace(Constants.HTTP, ""))
|
||||
.setCancelable(true)
|
||||
.setMessage(R.string.dialog_image)
|
||||
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
|
||||
.setNegativeButton(R.string.action_open, dialogClickListener)
|
||||
.setNeutralButton(R.string.action_download, dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void showLongPressLinkDialog(@NonNull final Context context, final String url) {
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInNewTab(url));
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url));
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("label", url);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context); // dialog
|
||||
builder.setTitle(url)
|
||||
.setCancelable(true)
|
||||
.setMessage(R.string.dialog_link)
|
||||
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
|
||||
.setNegativeButton(R.string.action_open, dialogClickListener)
|
||||
.setNeutralButton(R.string.action_copy, dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,46 +3,61 @@
|
||||
*/
|
||||
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.Build;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import acr.browser.lightning.BuildConfig;
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.MainActivity;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.bus.BrowserEvents;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
/**
|
||||
* Handle download requests
|
||||
*/
|
||||
public class DownloadHandler {
|
||||
|
||||
private static final String LOGTAG = "DLHandler";
|
||||
private static final String TAG = DownloadHandler.class.getSimpleName();
|
||||
private static final String COOKIE_REQUEST_HEADER = "Cookie";
|
||||
|
||||
public static final String DEFAULT_DOWNLOAD_PATH =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
.getPath();
|
||||
|
||||
|
||||
/**
|
||||
* 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 context The context in which the download was requested.
|
||||
* @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) {
|
||||
public static void onDownloadStart(@NonNull Context context, @NonNull PreferenceManager manager, String url, String userAgent,
|
||||
@Nullable 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
|
||||
@@ -52,18 +67,22 @@ public class DownloadHandler {
|
||||
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,
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setComponent(null);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
intent.setSelector(null);
|
||||
}
|
||||
ResolveInfo info = context.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)) {
|
||||
if (BuildConfig.APPLICATION_ID.equals(info.activityInfo.packageName)
|
||||
|| MainActivity.class.getName().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);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
// Best behavior is to fall back to a download in this
|
||||
@@ -72,14 +91,14 @@ public class DownloadHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
onDownloadStartNoStream(activity, url, userAgent, contentDisposition, mimetype
|
||||
);
|
||||
onDownloadStartNoStream(context, manager, 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) {
|
||||
@NonNull
|
||||
private static String encodePath(@NonNull String path) {
|
||||
char[] chars = path.toCharArray();
|
||||
|
||||
boolean needed = false;
|
||||
@@ -110,17 +129,18 @@ public class DownloadHandler {
|
||||
* 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 context The context in which the download is requested.
|
||||
* @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);
|
||||
private static void onDownloadStartNoStream(@NonNull final Context context, @NonNull PreferenceManager preferences,
|
||||
String url, String userAgent,
|
||||
String contentDisposition, @Nullable String mimetype) {
|
||||
final Bus eventBus = BrowserApp.getBus(context);
|
||||
final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype);
|
||||
|
||||
// Check to see if we have an SDCard
|
||||
String status = Environment.getExternalStorageState();
|
||||
@@ -130,14 +150,14 @@ public class DownloadHandler {
|
||||
|
||||
// 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);
|
||||
msg = context.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);
|
||||
msg = context.getString(R.string.download_no_sdcard_dlg_msg);
|
||||
title = R.string.download_no_sdcard_dlg_title;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(activity).setTitle(title)
|
||||
new AlertDialog.Builder(context).setTitle(title)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg)
|
||||
.setPositiveButton(R.string.action_ok, null).show();
|
||||
return;
|
||||
@@ -152,7 +172,8 @@ public class DownloadHandler {
|
||||
} 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);
|
||||
Log.e(TAG, "Exception while trying to parse url '" + url + '\'', e);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_download));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,7 +183,7 @@ public class DownloadHandler {
|
||||
try {
|
||||
request = new DownloadManager.Request(uri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Utils.showSnackbar(activity, R.string.cannot_download);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.cannot_download));
|
||||
return;
|
||||
}
|
||||
request.setMimeType(mimetype);
|
||||
@@ -170,41 +191,157 @@ public class DownloadHandler {
|
||||
// or, should it be set to one of several Environment.DIRECTORY* dirs
|
||||
// depending on mimetype?
|
||||
|
||||
String location = PreferenceManager.getInstance().getDownloadDirectory();
|
||||
request.setDestinationInExternalPublicDir(location, filename);
|
||||
String location = preferences.getDownloadDirectory();
|
||||
Uri downloadFolder;
|
||||
location = addNecessarySlashes(location);
|
||||
downloadFolder = Uri.parse(location);
|
||||
|
||||
File dir = new File(downloadFolder.getPath());
|
||||
if (!dir.isDirectory() && !dir.mkdirs()) {
|
||||
// Cannot make the directory
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWriteAccessAvailable(downloadFolder)) {
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download));
|
||||
return;
|
||||
}
|
||||
request.setDestinationUri(Uri.parse(Constants.FILE + location + filename));
|
||||
// let this downloaded file be scanned by MediaScanner - so that it can
|
||||
// show up in Gallery app, for example.
|
||||
request.setVisibleInDownloadsUi(true);
|
||||
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.addRequestHeader(COOKIE_REQUEST_HEADER, cookies);
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
if (mimetype == null) {
|
||||
Log.d(TAG, "Mimetype is 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();
|
||||
new FetchUrlMimeType(context, request, addressString, cookies, userAgent).start();
|
||||
} else {
|
||||
final DownloadManager manager = (DownloadManager) activity
|
||||
Log.d(TAG, "Valid mimetype, attempting to download");
|
||||
final DownloadManager manager = (DownloadManager) context
|
||||
.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);
|
||||
try {
|
||||
manager.enqueue(request);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Probably got a bad URL or something
|
||||
Log.e(TAG, "Unable to enqueue request", e);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.cannot_download));
|
||||
} catch (SecurityException e) {
|
||||
// TODO write a download utility that downloads files rather than rely on the system
|
||||
// because the system can only handle Environment.getExternal... as a path
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download));
|
||||
}
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(
|
||||
context.getString(R.string.download_pending) + ' ' + filename));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String sFileName = "test";
|
||||
private static final String sFileExtension = ".txt";
|
||||
|
||||
/**
|
||||
* Determine whether there is write access in the given directory. Returns false if a
|
||||
* file cannot be created in the directory or if the directory does not exist.
|
||||
*
|
||||
* @param directory the directory to check for write access
|
||||
* @return returns true if the directory can be written to or is in a directory that can
|
||||
* be written to. false if there is no write access.
|
||||
*/
|
||||
public static boolean isWriteAccessAvailable(@Nullable String directory) {
|
||||
if (directory == null || directory.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String dir = addNecessarySlashes(directory);
|
||||
dir = getFirstRealParentDirectory(dir);
|
||||
File file = new File(dir + sFileName + sFileExtension);
|
||||
for (int n = 0; n < 100; n++) {
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
if (file.createNewFile()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
file = new File(dir + sFileName + '-' + n + sFileExtension);
|
||||
}
|
||||
}
|
||||
return file.canWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first parent directory of a directory that exists. This is useful
|
||||
* for subdirectories that do not exist but their parents do.
|
||||
*
|
||||
* @param directory the directory to find the first existent parent
|
||||
* @return the first existent parent
|
||||
*/
|
||||
@Nullable
|
||||
private static String getFirstRealParentDirectory(@Nullable String directory) {
|
||||
while (true) {
|
||||
if (directory == null || directory.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
directory = addNecessarySlashes(directory);
|
||||
File file = new File(directory);
|
||||
if (!file.isDirectory()) {
|
||||
int indexSlash = directory.lastIndexOf('/');
|
||||
if (indexSlash > 0) {
|
||||
String parent = directory.substring(0, indexSlash);
|
||||
int previousIndex = parent.lastIndexOf('/');
|
||||
if (previousIndex > 0) {
|
||||
directory = parent.substring(0, previousIndex);
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
} else {
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) {
|
||||
File file = new File(fileUri.getPath());
|
||||
try {
|
||||
if (file.createNewFile()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String addNecessarySlashes(@Nullable String originalPath) {
|
||||
if (originalPath == null || originalPath.length() == 0) {
|
||||
return "/";
|
||||
}
|
||||
if (originalPath.charAt(originalPath.length() - 1) != '/') {
|
||||
originalPath = originalPath + '/';
|
||||
}
|
||||
if (originalPath.charAt(0) != '/') {
|
||||
originalPath = '/' + originalPath;
|
||||
}
|
||||
return originalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,19 +3,24 @@
|
||||
*/
|
||||
package acr.browser.lightning.download;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.bus.BrowserEvents;
|
||||
|
||||
/**
|
||||
* This class is used to pull down the http headers of a given URL so that we
|
||||
@@ -25,7 +30,7 @@ import acr.browser.lightning.utils.Utils;
|
||||
* 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 {
|
||||
class FetchUrlMimeType extends Thread {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@@ -37,20 +42,20 @@ public class FetchUrlMimeType extends Thread {
|
||||
|
||||
private final String mUserAgent;
|
||||
|
||||
public FetchUrlMimeType(Activity activity, DownloadManager.Request request, String uri,
|
||||
public FetchUrlMimeType(Context context, DownloadManager.Request request, String uri,
|
||||
String cookies, String userAgent) {
|
||||
mContext = activity.getApplicationContext();
|
||||
mContext = context;
|
||||
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.
|
||||
final Bus eventBus = BrowserApp.getBus(mContext);
|
||||
String mimeType = null;
|
||||
String contentDisposition = null;
|
||||
HttpURLConnection connection = null;
|
||||
@@ -79,7 +84,7 @@ public class FetchUrlMimeType extends Thread {
|
||||
contentDisposition = contentDispositionHeader;
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
} catch (@NonNull IllegalArgumentException | IOException ex) {
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
} finally {
|
||||
@@ -87,6 +92,7 @@ public class FetchUrlMimeType extends Thread {
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
String filename = "";
|
||||
if (mimeType != null) {
|
||||
if (mimeType.equalsIgnoreCase("text/plain")
|
||||
|| mimeType.equalsIgnoreCase("application/octet-stream")) {
|
||||
@@ -96,7 +102,7 @@ public class FetchUrlMimeType extends Thread {
|
||||
mRequest.setMimeType(newMimeType);
|
||||
}
|
||||
}
|
||||
String filename = URLUtil.guessFileName(mUri, contentDisposition, mimeType);
|
||||
filename = URLUtil.guessFileName(mUri, contentDisposition, mimeType);
|
||||
mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
|
||||
}
|
||||
|
||||
@@ -104,5 +110,13 @@ public class FetchUrlMimeType extends Thread {
|
||||
DownloadManager manager = (DownloadManager) mContext
|
||||
.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.enqueue(mRequest);
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
final String file = filename;
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(mContext.getString(R.string.download_pending) + ' ' + file));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
package acr.browser.lightning.download;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
@@ -11,43 +12,64 @@ import android.webkit.DownloadListener;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
|
||||
import com.anthonycr.grant.PermissionsManager;
|
||||
import com.anthonycr.grant.PermissionsResultAction;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LightningDownloadListener implements DownloadListener {
|
||||
|
||||
private final Activity mActivity;
|
||||
|
||||
public LightningDownloadListener(Activity activity) {
|
||||
mActivity = activity;
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
public LightningDownloadListener(Activity context) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mActivity = context;
|
||||
}
|
||||
|
||||
@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;
|
||||
final String contentDisposition, final String mimetype, long contentLength) {
|
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
new PermissionsResultAction() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
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, mPreferenceManager, url, userAgent,
|
||||
contentDisposition, mimetype);
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(String permission) {
|
||||
//TODO show message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
*/
|
||||
package acr.browser.lightning.download;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -11,39 +14,39 @@ import static android.util.Patterns.GOOD_IRI_CHAR;
|
||||
|
||||
/**
|
||||
* Web Address Parser
|
||||
*
|
||||
* <p/>
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* If given an https scheme but no port, fills in port
|
||||
*/
|
||||
public class WebAddress {
|
||||
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$_.+!*'(),;?&=]+)?)@)?" +
|
||||
private static final int MATCH_GROUP_SCHEME = 1;
|
||||
private static final int MATCH_GROUP_AUTHORITY = 2;
|
||||
private static final int MATCH_GROUP_HOST = 3;
|
||||
private static final int MATCH_GROUP_PORT = 4;
|
||||
private static final int MATCH_GROUP_PATH = 5;
|
||||
private 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 */"(\\/?[^#]*)?" +
|
||||
/* port */"(?::([0-9]*))?" +
|
||||
/* path */"(/?[^#]*)?" +
|
||||
/* anchor */".*", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* Parses given URI-like string.
|
||||
*/
|
||||
public WebAddress(String address) {
|
||||
public WebAddress(@Nullable String address) throws IllegalArgumentException {
|
||||
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("address can't be null");
|
||||
@@ -111,6 +114,7 @@ public class WebAddress {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -134,7 +138,7 @@ public class WebAddress {
|
||||
return mScheme;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
public void setHost(@NonNull String host) {
|
||||
mHost = host;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -16,9 +16,8 @@ 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 {
|
||||
public class AdvancedSettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String SETTINGS_NEWWINDOW = "allow_new_window";
|
||||
private static final String SETTINGS_ENABLECOOKIES = "allow_cookies";
|
||||
@@ -29,7 +28,6 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
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;
|
||||
@@ -46,8 +44,6 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
}
|
||||
|
||||
private void initPrefs() {
|
||||
// mPreferences storage
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
|
||||
renderingmode = findPreference(SETTINGS_RENDERINGMODE);
|
||||
textEncoding = findPreference(SETTINGS_TEXTENCODING);
|
||||
@@ -65,7 +61,7 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
cbcookiesInkognito.setOnPreferenceChangeListener(this);
|
||||
cbrestoreTabs.setOnPreferenceChangeListener(this);
|
||||
|
||||
switch (mPreferences.getRenderingMode()) {
|
||||
switch (mPreferenceManager.getRenderingMode()) {
|
||||
case 0:
|
||||
renderingmode.setSummary(getString(R.string.name_normal));
|
||||
break;
|
||||
@@ -78,22 +74,25 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
case 3:
|
||||
renderingmode.setSummary(getString(R.string.name_inverted_grayscale));
|
||||
break;
|
||||
case 4:
|
||||
renderingmode.setSummary(getString(R.string.name_increase_contrast));
|
||||
break;
|
||||
}
|
||||
|
||||
textEncoding.setSummary(mPreferences.getTextEncoding());
|
||||
textEncoding.setSummary(mPreferenceManager.getTextEncoding());
|
||||
|
||||
mUrlOptions = getResources().getStringArray(R.array.url_content_array);
|
||||
int option = mPreferences.getUrlBoxContentChoice();
|
||||
int option = mPreferenceManager.getUrlBoxContentChoice();
|
||||
urlcontent.setSummary(mUrlOptions[option]);
|
||||
|
||||
cbAllowPopups.setChecked(mPreferences.getPopupsEnabled());
|
||||
cbenablecookies.setChecked(mPreferences.getCookiesEnabled());
|
||||
cbcookiesInkognito.setChecked(mPreferences.getIncognitoCookiesEnabled());
|
||||
cbrestoreTabs.setChecked(mPreferences.getRestoreLostTabsEnabled());
|
||||
cbAllowPopups.setChecked(mPreferenceManager.getPopupsEnabled());
|
||||
cbenablecookies.setChecked(mPreferenceManager.getCookiesEnabled());
|
||||
cbcookiesInkognito.setChecked(mPreferenceManager.getIncognitoCookiesEnabled());
|
||||
cbrestoreTabs.setChecked(mPreferenceManager.getRestoreLostTabsEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_RENDERINGMODE:
|
||||
renderPicker();
|
||||
@@ -110,23 +109,23 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
// switch preferences
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_NEWWINDOW:
|
||||
mPreferences.setPopupsEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setPopupsEnabled((Boolean) newValue);
|
||||
cbAllowPopups.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_ENABLECOOKIES:
|
||||
mPreferences.setCookiesEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setCookiesEnabled((Boolean) newValue);
|
||||
cbenablecookies.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_COOKIESINKOGNITO:
|
||||
mPreferences.setIncognitoCookiesEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setIncognitoCookiesEnabled((Boolean) newValue);
|
||||
cbcookiesInkognito.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_RESTORETABS:
|
||||
mPreferences.setRestoreLostTabsEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setRestoreLostTabsEnabled((Boolean) newValue);
|
||||
cbrestoreTabs.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
default:
|
||||
@@ -140,14 +139,15 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
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)};
|
||||
mActivity.getString(R.string.name_inverted_grayscale),
|
||||
mActivity.getString(R.string.name_increase_contrast)};
|
||||
|
||||
int n = mPreferences.getRenderingMode();
|
||||
int n = mPreferenceManager.getRenderingMode();
|
||||
|
||||
picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setRenderingMode(which);
|
||||
mPreferenceManager.setRenderingMode(which);
|
||||
switch (which) {
|
||||
case 0:
|
||||
renderingmode.setSummary(getString(R.string.name_normal));
|
||||
@@ -161,16 +161,13 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
case 3:
|
||||
renderingmode.setSummary(getString(R.string.name_inverted_grayscale));
|
||||
break;
|
||||
case 4:
|
||||
renderingmode.setSummary(getString(R.string.name_increase_contrast));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
@@ -178,22 +175,16 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
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());
|
||||
int n = textEncodingList.indexOf(mPreferenceManager.getTextEncoding());
|
||||
|
||||
picker.setSingleChoiceItems(Constants.TEXT_ENCODINGS, n, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setTextEncoding(Constants.TEXT_ENCODINGS[which]);
|
||||
mPreferenceManager.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.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
@@ -201,24 +192,18 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref
|
||||
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
|
||||
picker.setTitle(getResources().getString(R.string.url_contents));
|
||||
|
||||
int n = mPreferences.getUrlBoxContentChoice();
|
||||
int n = mPreferenceManager.getUrlBoxContentChoice();
|
||||
|
||||
picker.setSingleChoiceItems(mUrlOptions, n, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setUrlBoxContentChoice(which);
|
||||
mPreferenceManager.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.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,84 +6,329 @@ package acr.browser.lightning.fragment;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.anthonycr.grant.PermissionsManager;
|
||||
import com.anthonycr.grant.PermissionsResultAction;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.database.BookmarkLocalSync;
|
||||
import acr.browser.lightning.database.BookmarkLocalSync.Source;
|
||||
import acr.browser.lightning.database.BookmarkManager;
|
||||
import acr.browser.lightning.utils.PermissionsManager;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.react.OnSubscribe;
|
||||
import acr.browser.lightning.react.Schedulers;
|
||||
import acr.browser.lightning.utils.Preconditions;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
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 static final String SETTINGS_IMPORT_BROWSER = "import_browser";
|
||||
private static final String SETTINGS_DELETE_BOOKMARKS = "delete_bookmarks";
|
||||
|
||||
private Activity mActivity;
|
||||
private BookmarkManager mBookmarkManager;
|
||||
@Nullable private Activity mActivity;
|
||||
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
private File[] mFileList;
|
||||
private String[] mFileNameList;
|
||||
private PermissionsManager mPermissionsManager;
|
||||
private static String[] REQUIRED_PERMISSIONS = new String[]{
|
||||
@Nullable private BookmarkLocalSync mSync;
|
||||
|
||||
private static final 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());
|
||||
|
||||
private class ImportBookmarksTask extends AsyncTask<Void, Void, Integer> {
|
||||
|
||||
@NonNull private final WeakReference<Activity> mActivityReference;
|
||||
private final Source mSource;
|
||||
|
||||
public ImportBookmarksTask(Activity activity, Source source) {
|
||||
mActivityReference = new WeakReference<>(activity);
|
||||
mSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
List<HistoryItem> list;
|
||||
Log.d(Constants.TAG, "Loading bookmarks from: " + mSource.name());
|
||||
switch (mSource) {
|
||||
case STOCK:
|
||||
list = getSync().getBookmarksFromStockBrowser();
|
||||
break;
|
||||
case CHROME_STABLE:
|
||||
list = getSync().getBookmarksFromChrome();
|
||||
break;
|
||||
case CHROME_BETA:
|
||||
list = getSync().getBookmarksFromChromeBeta();
|
||||
break;
|
||||
case CHROME_DEV:
|
||||
list = getSync().getBookmarksFromChromeDev();
|
||||
break;
|
||||
default:
|
||||
list = new ArrayList<>(0);
|
||||
break;
|
||||
}
|
||||
int count = 0;
|
||||
if (!list.isEmpty()) {
|
||||
mBookmarkManager.addBookmarkList(list);
|
||||
count = list.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer num) {
|
||||
super.onPostExecute(num);
|
||||
Activity activity = mActivityReference.get();
|
||||
if (activity != null) {
|
||||
int number = num;
|
||||
final String message = activity.getResources().getString(R.string.message_import);
|
||||
Utils.showSnackbar(activity, number + " " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private BookmarkLocalSync getSync() {
|
||||
Preconditions.checkNonNull(mActivity);
|
||||
if (mSync == null) {
|
||||
mSync = new BookmarkLocalSync(mActivity);
|
||||
}
|
||||
|
||||
return mSync;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preference_bookmarks);
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
mBookmarkManager = BookmarkManager.getInstance(mActivity.getApplicationContext());
|
||||
mSync = new BookmarkLocalSync(mActivity);
|
||||
|
||||
initPrefs();
|
||||
|
||||
mPermissionsManager = PermissionsManager.getInstance();
|
||||
PermissionsManager permissionsManager = PermissionsManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
mPermissionsManager.requestPermissionsIfNecessary(getActivity(), REQUIRED_PERMISSIONS);
|
||||
permissionsManager.requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mActivity = null;
|
||||
}
|
||||
|
||||
private void initPrefs() {
|
||||
|
||||
Preference exportpref = findPreference(SETTINGS_EXPORT);
|
||||
Preference importpref = findPreference(SETTINGS_IMPORT);
|
||||
Preference exportPref = findPreference(SETTINGS_EXPORT);
|
||||
Preference importPref = findPreference(SETTINGS_IMPORT);
|
||||
Preference deletePref = findPreference(SETTINGS_DELETE_BOOKMARKS);
|
||||
|
||||
exportPref.setOnPreferenceClickListener(this);
|
||||
importPref.setOnPreferenceClickListener(this);
|
||||
deletePref.setOnPreferenceClickListener(this);
|
||||
|
||||
BrowserApp.getTaskThread().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean isBrowserImportSupported = getSync().isBrowserImportSupported();
|
||||
Schedulers.main().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Preference importStock = findPreference(SETTINGS_IMPORT_BROWSER);
|
||||
importStock.setEnabled(isBrowserImportSupported);
|
||||
importStock.setOnPreferenceClickListener(BookmarkSettingsFragment.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
exportpref.setOnPreferenceClickListener(this);
|
||||
importpref.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_EXPORT:
|
||||
if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) {
|
||||
mBookmarkManager.exportBookmarks(getActivity());
|
||||
}
|
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS,
|
||||
new PermissionsResultAction() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
mBookmarkManager.exportBookmarks(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(String permission) {
|
||||
//TODO Show message
|
||||
}
|
||||
});
|
||||
return true;
|
||||
case SETTINGS_IMPORT:
|
||||
if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) {
|
||||
loadFileList(null);
|
||||
createDialog();
|
||||
}
|
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS,
|
||||
new PermissionsResultAction() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
loadFileList(null);
|
||||
createDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(String permission) {
|
||||
//TODO Show message
|
||||
}
|
||||
});
|
||||
return true;
|
||||
case SETTINGS_IMPORT_BROWSER:
|
||||
getSync().getSupportedBrowsers().subscribeOn(Schedulers.worker())
|
||||
.observeOn(Schedulers.main()).subscribe(new OnSubscribe<List<Source>>() {
|
||||
@Override
|
||||
public void onNext(@Nullable List<Source> items) {
|
||||
Activity activity = getActivity();
|
||||
if (items == null || activity == null) {
|
||||
return;
|
||||
}
|
||||
List<String> titles = buildTitleList(activity, items);
|
||||
showChooserDialog(activity, titles);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
case SETTINGS_DELETE_BOOKMARKS:
|
||||
showDeleteBookmarksDialog();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFileList(File path) {
|
||||
private void showDeleteBookmarksDialog() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(R.string.action_delete);
|
||||
builder.setMessage(R.string.action_delete_all_bookmarks);
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mBookmarkManager.deleteAllBookmarks();
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<String> buildTitleList(@NonNull Activity activity, @NonNull List<Source> items) {
|
||||
List<String> titles = new ArrayList<>();
|
||||
String title;
|
||||
for (Source source : items) {
|
||||
switch (source) {
|
||||
case STOCK:
|
||||
titles.add(getString(R.string.stock_browser));
|
||||
break;
|
||||
case CHROME_STABLE:
|
||||
title = getTitle(activity, "com.android.chrome");
|
||||
if (title != null) {
|
||||
titles.add(title);
|
||||
}
|
||||
break;
|
||||
case CHROME_BETA:
|
||||
title = getTitle(activity, "com.chrome.beta");
|
||||
if (title != null) {
|
||||
titles.add(title);
|
||||
}
|
||||
break;
|
||||
case CHROME_DEV:
|
||||
title = getTitle(activity, "com.chrome.beta");
|
||||
if (title != null) {
|
||||
titles.add(title);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
|
||||
private void showChooserDialog(final Activity activity, List<String> list) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(activity,
|
||||
android.R.layout.simple_list_item_1);
|
||||
for (String title : list) {
|
||||
adapter.add(title);
|
||||
}
|
||||
builder.setTitle(R.string.supported_browsers_title);
|
||||
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String title = adapter.getItem(which);
|
||||
Source source = null;
|
||||
if (title.equals(getString(R.string.stock_browser))) {
|
||||
source = Source.STOCK;
|
||||
} else if (title.equals(getTitle(activity, "com.android.chrome"))) {
|
||||
source = Source.CHROME_STABLE;
|
||||
} else if (title.equals(getTitle(activity, "com.android.beta"))) {
|
||||
source = Source.CHROME_BETA;
|
||||
} else if (title.equals(getTitle(activity, "com.android.dev"))) {
|
||||
source = Source.CHROME_DEV;
|
||||
}
|
||||
if (source != null) {
|
||||
new ImportBookmarksTask(activity, source).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getTitle(@NonNull Activity activity, @NonNull String packageName) {
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||
CharSequence title = pm.getApplicationLabel(info);
|
||||
if (title != null) {
|
||||
return title.toString();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadFileList(@Nullable File path) {
|
||||
File file;
|
||||
if (path != null) {
|
||||
file = path;
|
||||
@@ -113,10 +358,10 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
}
|
||||
}
|
||||
|
||||
private class SortName implements Comparator<File> {
|
||||
private static class SortName implements Comparator<File> {
|
||||
|
||||
@Override
|
||||
public int compare(File a, File b) {
|
||||
public int compare(@NonNull File a, @NonNull File b) {
|
||||
if (a.isDirectory() && b.isDirectory())
|
||||
return a.getName().compareTo(b.getName());
|
||||
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
package acr.browser.lightning.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Transformation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.ReadingActivity;
|
||||
import acr.browser.lightning.activity.TabsManager;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.async.AsyncExecutor;
|
||||
import acr.browser.lightning.bus.BookmarkEvents;
|
||||
import acr.browser.lightning.bus.BrowserEvents;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.controller.UIController;
|
||||
import acr.browser.lightning.database.BookmarkManager;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.dialog.LightningDialogBuilder;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.async.ImageDownloadTask;
|
||||
import acr.browser.lightning.react.Action;
|
||||
import acr.browser.lightning.react.Observable;
|
||||
import acr.browser.lightning.react.OnSubscribe;
|
||||
import acr.browser.lightning.react.Schedulers;
|
||||
import acr.browser.lightning.react.Subscriber;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public class BookmarksFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
private final static String TAG = BookmarksFragment.class.getSimpleName();
|
||||
|
||||
public final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE";
|
||||
|
||||
// Managers
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
|
||||
// Event bus
|
||||
@Inject Bus mEventBus;
|
||||
|
||||
// Dialog builder
|
||||
@Inject LightningDialogBuilder mBookmarksDialogBuilder;
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
private TabsManager mTabsManager;
|
||||
|
||||
// Adapter
|
||||
private BookmarkViewAdapter mBookmarkAdapter;
|
||||
|
||||
// Preloaded images
|
||||
private Bitmap mWebpageBitmap, mFolderBitmap;
|
||||
|
||||
// Bookmarks
|
||||
private final List<HistoryItem> mBookmarks = new ArrayList<>();
|
||||
|
||||
// Views
|
||||
private ListView mBookmarksListView;
|
||||
private ImageView mBookmarkTitleImage, mBookmarkImage;
|
||||
|
||||
// Colors
|
||||
private int mIconColor, mScrollIndex;
|
||||
|
||||
private boolean mIsIncognito;
|
||||
|
||||
private Observable<BookmarkViewAdapter> initBookmarkManager() {
|
||||
return Observable.create(new Action<BookmarkViewAdapter>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscriber<BookmarkViewAdapter> subscriber) {
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
mBookmarkAdapter = new BookmarkViewAdapter(context, mBookmarks);
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
subscriber.onNext(mBookmarkAdapter);
|
||||
}
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
final Bundle arguments = getArguments();
|
||||
final Context context = getContext();
|
||||
mTabsManager = ((UIController) context).getTabModel();
|
||||
mIsIncognito = arguments.getBoolean(INCOGNITO_MODE, false);
|
||||
boolean darkTheme = mPreferenceManager.getUseTheme() != 0 || mIsIncognito;
|
||||
mWebpageBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme);
|
||||
mFolderBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_folder, darkTheme);
|
||||
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) :
|
||||
ThemeUtils.getIconLightThemeColor(context);
|
||||
}
|
||||
|
||||
// Handle bookmark click
|
||||
private final OnItemClickListener mItemClickListener = new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final HistoryItem item = mBookmarks.get(position);
|
||||
if (item.isFolder()) {
|
||||
mScrollIndex = mBookmarksListView.getFirstVisiblePosition();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true), true);
|
||||
} else {
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(item.getUrl()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final HistoryItem item = mBookmarks.get(position);
|
||||
handleLongPress(item);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (mBookmarkAdapter != null) {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.bookmark_drawer, container, false);
|
||||
mBookmarksListView = (ListView) view.findViewById(R.id.right_drawer_list);
|
||||
mBookmarksListView.setOnItemClickListener(mItemClickListener);
|
||||
mBookmarksListView.setOnItemLongClickListener(mItemLongClickListener);
|
||||
mBookmarkTitleImage = (ImageView) view.findViewById(R.id.starIcon);
|
||||
mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
mBookmarkImage = (ImageView) view.findViewById(R.id.icon_star);
|
||||
final View backView = view.findViewById(R.id.bookmark_back_button);
|
||||
backView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mBookmarkManager == null) return;
|
||||
if (!mBookmarkManager.isRootFolder()) {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
|
||||
mBookmarksListView.setSelection(mScrollIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
setupNavigationButton(view, R.id.action_add_bookmark, R.id.icon_star);
|
||||
setupNavigationButton(view, R.id.action_reading, R.id.icon_reading);
|
||||
setupNavigationButton(view, R.id.action_toggle_desktop, R.id.icon_desktop);
|
||||
|
||||
initBookmarkManager().subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.main())
|
||||
.subscribe(new OnSubscribe<BookmarkViewAdapter>() {
|
||||
@Override
|
||||
public void onNext(@Nullable BookmarkViewAdapter item) {
|
||||
mBookmarksListView.setAdapter(mBookmarkAdapter);
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mEventBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
mEventBus.unregister(this);
|
||||
}
|
||||
|
||||
public void reinitializePreferences() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
boolean darkTheme = mPreferenceManager.getUseTheme() != 0 || mIsIncognito;
|
||||
mWebpageBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_webpage, darkTheme);
|
||||
mFolderBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_folder, darkTheme);
|
||||
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) :
|
||||
ThemeUtils.getIconLightThemeColor(activity);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void addBookmark(@NonNull final BrowserEvents.BookmarkAdded event) {
|
||||
updateBookmarkIndicator(event.url);
|
||||
String folder = mBookmarkManager.getCurrentFolder();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(folder, true), false);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void currentPageInfo(@NonNull final BrowserEvents.CurrentPageUrl event) {
|
||||
updateBookmarkIndicator(event.url);
|
||||
String folder = mBookmarkManager.getCurrentFolder();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(folder, true), false);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void bookmarkChanged(BookmarkEvents.BookmarkChanged event) {
|
||||
String folder = mBookmarkManager.getCurrentFolder();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(folder, true), false);
|
||||
}
|
||||
|
||||
private void updateBookmarkIndicator(final String url) {
|
||||
if (!mBookmarkManager.isBookmark(url)) {
|
||||
mBookmarkImage.setImageResource(R.drawable.ic_action_star);
|
||||
mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
mBookmarkImage.setImageResource(R.drawable.ic_bookmark);
|
||||
mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(getContext()), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void userPressedBack(final BrowserEvents.UserPressedBack event) {
|
||||
if (mBookmarkManager.isRootFolder()) {
|
||||
mEventBus.post(new BookmarkEvents.CloseBookmarks());
|
||||
} else {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
|
||||
mBookmarksListView.setSelection(mScrollIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void bookmarkDeleted(@NonNull final BookmarkEvents.Deleted event) {
|
||||
mBookmarks.remove(event.item);
|
||||
if (event.item.isFolder()) {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
} else {
|
||||
mBookmarkAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBookmarkDataSet(@NonNull List<HistoryItem> items, boolean animate) {
|
||||
mBookmarks.clear();
|
||||
mBookmarks.addAll(items);
|
||||
mBookmarkAdapter.notifyDataSetChanged();
|
||||
final int resource;
|
||||
if (mBookmarkManager.isRootFolder()) {
|
||||
resource = R.drawable.ic_action_star;
|
||||
} else {
|
||||
resource = R.drawable.ic_action_back;
|
||||
}
|
||||
|
||||
final Animation startRotation = new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
mBookmarkTitleImage.setRotationY(90 * interpolatedTime);
|
||||
}
|
||||
};
|
||||
final Animation finishRotation = new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
mBookmarkTitleImage.setRotationY((-90) + (90 * interpolatedTime));
|
||||
}
|
||||
};
|
||||
startRotation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mBookmarkTitleImage.setImageResource(resource);
|
||||
mBookmarkTitleImage.startAnimation(finishRotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
});
|
||||
startRotation.setInterpolator(new AccelerateInterpolator());
|
||||
finishRotation.setInterpolator(new DecelerateInterpolator());
|
||||
startRotation.setDuration(250);
|
||||
finishRotation.setDuration(250);
|
||||
|
||||
if (animate) {
|
||||
mBookmarkTitleImage.startAnimation(startRotation);
|
||||
} else {
|
||||
mBookmarkTitleImage.setImageResource(resource);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupNavigationButton(@NonNull View view, @IdRes int buttonId, @IdRes int imageId) {
|
||||
FrameLayout frameButton = (FrameLayout) view.findViewById(buttonId);
|
||||
frameButton.setOnClickListener(this);
|
||||
frameButton.setOnLongClickListener(this);
|
||||
ImageView buttonImage = (ImageView) view.findViewById(imageId);
|
||||
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
private void handleLongPress(@NonNull final HistoryItem item) {
|
||||
if (item.isFolder()) {
|
||||
mBookmarksDialogBuilder.showBookmarkFolderLongPressedDialog(getContext(), item);
|
||||
} else {
|
||||
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(getContext(), item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NonNull View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.action_add_bookmark:
|
||||
mEventBus.post(new BookmarkEvents.ToggleBookmarkForCurrentPage());
|
||||
break;
|
||||
case R.id.action_reading:
|
||||
LightningView currentTab = mTabsManager.getCurrentTab();
|
||||
if (currentTab != null) {
|
||||
Intent read = new Intent(getActivity(), ReadingActivity.class);
|
||||
read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl());
|
||||
startActivity(read);
|
||||
}
|
||||
break;
|
||||
case R.id.action_toggle_desktop:
|
||||
LightningView current = mTabsManager.getCurrentTab();
|
||||
if (current != null) {
|
||||
current.toggleDesktopUA(getActivity());
|
||||
current.reload();
|
||||
// TODO add back drawer closing
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private class BookmarkViewAdapter extends ArrayAdapter<HistoryItem> {
|
||||
|
||||
final Context context;
|
||||
|
||||
public BookmarkViewAdapter(Context context, @NonNull List<HistoryItem> data) {
|
||||
super(context, R.layout.bookmark_list_item, data);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View row = convertView;
|
||||
BookmarkViewHolder holder;
|
||||
|
||||
if (row == null) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
row = inflater.inflate(R.layout.bookmark_list_item, parent, false);
|
||||
|
||||
holder = new BookmarkViewHolder();
|
||||
holder.txtTitle = (TextView) row.findViewById(R.id.textBookmark);
|
||||
holder.favicon = (ImageView) row.findViewById(R.id.faviconBookmark);
|
||||
row.setTag(holder);
|
||||
} else {
|
||||
holder = (BookmarkViewHolder) row.getTag();
|
||||
}
|
||||
|
||||
ViewCompat.jumpDrawablesToCurrentState(row);
|
||||
|
||||
HistoryItem web = mBookmarks.get(position);
|
||||
holder.txtTitle.setText(web.getTitle());
|
||||
if (web.isFolder()) {
|
||||
holder.favicon.setImageBitmap(mFolderBitmap);
|
||||
} else if (web.getBitmap() == null) {
|
||||
holder.favicon.setImageBitmap(mWebpageBitmap);
|
||||
new ImageDownloadTask(holder.favicon, web, mWebpageBitmap, context)
|
||||
.executeOnExecutor(AsyncExecutor.getInstance());
|
||||
} else {
|
||||
holder.favicon.setImageBitmap(web.getBitmap());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private class BookmarkViewHolder {
|
||||
TextView txtTitle;
|
||||
ImageView favicon;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package acr.browser.lightning.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
public class DebugSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String LEAK_CANARY = "leak_canary_enabled";
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
private SwitchPreference mSwitchLeakCanary;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
addPreferencesFromResource(R.xml.preference_debug);
|
||||
|
||||
mSwitchLeakCanary = (SwitchPreference) findPreference(LEAK_CANARY);
|
||||
mSwitchLeakCanary.setChecked(mPreferenceManager.getUseLeakCanary());
|
||||
mSwitchLeakCanary.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, @NonNull Object newValue) {
|
||||
switch (preference.getKey()) {
|
||||
case LEAK_CANARY:
|
||||
boolean value = Boolean.TRUE.equals(newValue);
|
||||
mPreferenceManager.setUseLeakCanary(value);
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
Utils.showSnackbar(activity, R.string.app_restart);
|
||||
}
|
||||
mSwitchLeakCanary.setChecked(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -19,9 +19,8 @@ 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 {
|
||||
public class DisplaySettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String SETTINGS_HIDESTATUSBAR = "fullScreenOption";
|
||||
private static final String SETTINGS_FULLSCREEN = "fullscreen";
|
||||
@@ -38,7 +37,6 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
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;
|
||||
@@ -57,9 +55,8 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
|
||||
private void initPrefs() {
|
||||
// mPreferences storage
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
mThemeOptions = this.getResources().getStringArray(R.array.themes);
|
||||
mCurrentTheme = mPreferences.getUseTheme();
|
||||
mCurrentTheme = mPreferenceManager.getUseTheme();
|
||||
|
||||
theme = findPreference(SETTINGS_THEME);
|
||||
Preference textsize = findPreference(SETTINGS_TEXTSIZE);
|
||||
@@ -77,17 +74,17 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
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());
|
||||
cbstatus.setChecked(mPreferenceManager.getHideStatusBarEnabled());
|
||||
cbfullscreen.setChecked(mPreferenceManager.getFullScreenEnabled());
|
||||
cbviewport.setChecked(mPreferenceManager.getUseWideViewportEnabled());
|
||||
cboverview.setChecked(mPreferenceManager.getOverviewModeEnabled());
|
||||
cbreflow.setChecked(mPreferenceManager.getTextReflowEnabled());
|
||||
|
||||
theme.setSummary(mThemeOptions[mPreferences.getUseTheme()]);
|
||||
theme.setSummary(mThemeOptions[mPreferenceManager.getUseTheme()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_THEME:
|
||||
themePicker();
|
||||
@@ -101,27 +98,27 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
// switch preferences
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_HIDESTATUSBAR:
|
||||
mPreferences.setHideStatusBarEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setHideStatusBarEnabled((Boolean) newValue);
|
||||
cbstatus.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_FULLSCREEN:
|
||||
mPreferences.setFullScreenEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setFullScreenEnabled((Boolean) newValue);
|
||||
cbfullscreen.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_VIEWPORT:
|
||||
mPreferences.setUseWideViewportEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setUseWideViewportEnabled((Boolean) newValue);
|
||||
cbviewport.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_OVERVIEWMODE:
|
||||
mPreferences.setOverviewModeEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setOverviewModeEnabled((Boolean) newValue);
|
||||
cboverview.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_REFLOW:
|
||||
mPreferences.setTextReflowEnabled((Boolean) newValue);
|
||||
mPreferenceManager.setTextReflowEnabled((Boolean) newValue);
|
||||
cbreflow.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
default:
|
||||
@@ -139,39 +136,24 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
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) {
|
||||
}
|
||||
|
||||
});
|
||||
bar.setOnSeekBarChangeListener(new TextSeekBarListener(sample));
|
||||
final int MAX = 5;
|
||||
bar.setMax(MAX);
|
||||
bar.setProgress(MAX - mPreferences.getTextSize());
|
||||
bar.setProgress(MAX - mPreferenceManager.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());
|
||||
mPreferenceManager.setTextSize(MAX - bar.getProgress());
|
||||
}
|
||||
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private float getTextSize(int size) {
|
||||
private static float getTextSize(int size) {
|
||||
switch (size) {
|
||||
case 0:
|
||||
return XSMALL;
|
||||
@@ -194,12 +176,12 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
|
||||
picker.setTitle(getResources().getString(R.string.theme));
|
||||
|
||||
int n = mPreferences.getUseTheme();
|
||||
int n = mPreferenceManager.getUseTheme();
|
||||
picker.setSingleChoiceItems(mThemeOptions, n, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setUseTheme(which);
|
||||
mPreferenceManager.setUseTheme(which);
|
||||
if (which < mThemeOptions.length) {
|
||||
theme.setSummary(mThemeOptions[which]);
|
||||
}
|
||||
@@ -210,7 +192,7 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (mCurrentTheme != mPreferences.getUseTheme()) {
|
||||
if (mCurrentTheme != mPreferenceManager.getUseTheme()) {
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
@@ -218,11 +200,32 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
picker.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
if (mCurrentTheme != mPreferences.getUseTheme()) {
|
||||
if (mCurrentTheme != mPreferenceManager.getUseTheme()) {
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
picker.show();
|
||||
}
|
||||
|
||||
private static class TextSeekBarListener implements SeekBar.OnSeekBarChangeListener {
|
||||
|
||||
private final TextView sample;
|
||||
|
||||
public TextSeekBarListener(TextView sample) {this.sample = sample;}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar view, int size, boolean user) {
|
||||
this.sample.setTextSize(getTextSize(size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar arg0) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,30 @@ 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.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.download.DownloadHandler;
|
||||
import acr.browser.lightning.utils.ProxyUtils;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
public class GeneralSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
public class GeneralSettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String SETTINGS_PROXY = "proxy";
|
||||
private static final String SETTINGS_FLASH = "cb_flash";
|
||||
@@ -45,13 +45,11 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
|
||||
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) {
|
||||
@@ -65,22 +63,19 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
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);
|
||||
CheckBoxPreference cbFlash = (CheckBoxPreference) findPreference(SETTINGS_FLASH);
|
||||
CheckBoxPreference cbAds = (CheckBoxPreference) findPreference(SETTINGS_ADS);
|
||||
CheckBoxPreference cbImages = (CheckBoxPreference) findPreference(SETTINGS_IMAGES);
|
||||
CheckBoxPreference cbJsScript = (CheckBoxPreference) findPreference(SETTINGS_JAVASCRIPT);
|
||||
CheckBoxPreference cbColorMode = (CheckBoxPreference) findPreference(SETTINGS_COLORMODE);
|
||||
CheckBoxPreference cbgooglesuggest = (CheckBoxPreference) findPreference(SETTINGS_GOOGLESUGGESTIONS);
|
||||
CheckBoxPreference cbDrawerTabs = (CheckBoxPreference) findPreference(SETTINGS_DRAWERTABS);
|
||||
|
||||
proxy.setOnPreferenceClickListener(this);
|
||||
useragent.setOnPreferenceClickListener(this);
|
||||
@@ -95,25 +90,25 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
cbgooglesuggest.setOnPreferenceChangeListener(this);
|
||||
cbDrawerTabs.setOnPreferenceChangeListener(this);
|
||||
|
||||
mAgentChoice = mPreferences.getUserAgentChoice();
|
||||
mHomepage = mPreferences.getHomepage();
|
||||
mDownloadLocation = mPreferences.getDownloadDirectory();
|
||||
mAgentChoice = mPreferenceManager.getUserAgentChoice();
|
||||
mHomepage = mPreferenceManager.getHomepage();
|
||||
mDownloadLocation = mPreferenceManager.getDownloadDirectory();
|
||||
mProxyChoices = getResources().getStringArray(R.array.proxy_choices_array);
|
||||
|
||||
int choice = mPreferences.getProxyChoice();
|
||||
int choice = mPreferenceManager.getProxyChoice();
|
||||
if (choice == Constants.PROXY_MANUAL) {
|
||||
proxy.setSummary(mPreferences.getProxyHost() + ':' + mPreferences.getProxyPort());
|
||||
proxy.setSummary(mPreferenceManager.getProxyHost() + ':' + mPreferenceManager.getProxyPort());
|
||||
} else {
|
||||
proxy.setSummary(mProxyChoices[choice]);
|
||||
}
|
||||
|
||||
if (API >= 19) {
|
||||
mPreferences.setFlashSupport(0);
|
||||
if (API >= Build.VERSION_CODES.KITKAT) {
|
||||
mPreferenceManager.setFlashSupport(0);
|
||||
}
|
||||
|
||||
setSearchEngineSummary(mPreferences.getSearchChoice());
|
||||
setSearchEngineSummary(mPreferenceManager.getSearchChoice());
|
||||
|
||||
downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' + mDownloadLocation);
|
||||
downloadloc.setSummary(mDownloadLocation);
|
||||
|
||||
if (mHomepage.contains("about:home")) {
|
||||
home.setSummary(getResources().getString(R.string.action_homepage));
|
||||
@@ -139,28 +134,27 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
useragent.setSummary(getResources().getString(R.string.agent_custom));
|
||||
}
|
||||
|
||||
int flashNum = mPreferences.getFlashSupport();
|
||||
boolean imagesBool = mPreferences.getBlockImagesEnabled();
|
||||
boolean enableJSBool = mPreferences.getJavaScriptEnabled();
|
||||
int flashNum = mPreferenceManager.getFlashSupport();
|
||||
boolean imagesBool = mPreferenceManager.getBlockImagesEnabled();
|
||||
boolean enableJSBool = mPreferenceManager.getJavaScriptEnabled();
|
||||
|
||||
proxy.setEnabled(Constants.FULL_VERSION);
|
||||
cbAds.setEnabled(Constants.FULL_VERSION);
|
||||
cbFlash.setEnabled(API < 19);
|
||||
cbFlash.setEnabled(API < Build.VERSION_CODES.KITKAT);
|
||||
|
||||
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));
|
||||
cbAds.setChecked(Constants.FULL_VERSION && mPreferenceManager.getAdBlockEnabled());
|
||||
cbColorMode.setChecked(mPreferenceManager.getColorModeEnabled());
|
||||
cbgooglesuggest.setChecked(mPreferenceManager.getGoogleSearchSuggestionsEnabled());
|
||||
cbDrawerTabs.setChecked(mPreferenceManager.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();
|
||||
String mSearchUrl = mPreferenceManager.getSearchUrl();
|
||||
getSearchUrl.setText(mSearchUrl);
|
||||
urlPicker.setView(getSearchUrl);
|
||||
urlPicker.setPositiveButton(getResources().getString(R.string.action_ok),
|
||||
@@ -168,7 +162,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String text = getSearchUrl.getText().toString();
|
||||
mPreferences.setSearchUrl(text);
|
||||
mPreferenceManager.setSearchUrl(text);
|
||||
searchengine.setSummary(getResources().getString(R.string.custom_url) + ": "
|
||||
+ text);
|
||||
}
|
||||
@@ -185,7 +179,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mPreferences.setFlashSupport(1);
|
||||
mPreferenceManager.setFlashSupport(1);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getResources().getString(R.string.action_auto),
|
||||
@@ -193,13 +187,13 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setFlashSupport(2);
|
||||
mPreferenceManager.setFlashSupport(2);
|
||||
}
|
||||
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
mPreferences.setFlashSupport(0);
|
||||
mPreferenceManager.setFlashSupport(0);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -210,7 +204,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
private void proxyChoicePicker() {
|
||||
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
|
||||
picker.setTitle(getResources().getString(R.string.http_proxy));
|
||||
picker.setSingleChoiceItems(mProxyChoices, mPreferences.getProxyChoice(),
|
||||
picker.setSingleChoiceItems(mProxyChoices, mPreferenceManager.getProxyChoice(),
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
@@ -218,30 +212,24 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
setProxyChoice(which);
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
private void setProxyChoice(int choice) {
|
||||
ProxyUtils utils = ProxyUtils.getInstance(mActivity);
|
||||
switch (choice) {
|
||||
case Constants.PROXY_ORBOT:
|
||||
choice = utils.setProxyChoice(choice, mActivity);
|
||||
choice = ProxyUtils.setProxyChoice(choice, mActivity);
|
||||
break;
|
||||
case Constants.PROXY_I2P:
|
||||
choice = utils.setProxyChoice(choice, mActivity);
|
||||
choice = ProxyUtils.setProxyChoice(choice, mActivity);
|
||||
break;
|
||||
case Constants.PROXY_MANUAL:
|
||||
manualProxyPicker();
|
||||
break;
|
||||
}
|
||||
|
||||
mPreferences.setProxyChoice(choice);
|
||||
mPreferenceManager.setProxyChoice(choice);
|
||||
if (choice < mProxyChoices.length)
|
||||
proxy.setSummary(mProxyChoices[choice]);
|
||||
}
|
||||
@@ -259,8 +247,8 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
filterArray[0] = new InputFilter.LengthFilter(maxCharacters - 1);
|
||||
eProxyPort.setFilters(filterArray);
|
||||
|
||||
eProxyHost.setText(mPreferences.getProxyHost());
|
||||
eProxyPort.setText(Integer.toString(mPreferences.getProxyPort()));
|
||||
eProxyHost.setText(mPreferenceManager.getProxyHost());
|
||||
eProxyPort.setText(Integer.toString(mPreferenceManager.getProxyPort()));
|
||||
|
||||
new AlertDialog.Builder(mActivity)
|
||||
.setTitle(R.string.manual_proxy)
|
||||
@@ -275,10 +263,10 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
// larger than max integer
|
||||
proxyPort = Integer.parseInt(eProxyPort.getText().toString());
|
||||
} catch (NumberFormatException ignored) {
|
||||
proxyPort = mPreferences.getProxyPort();
|
||||
proxyPort = mPreferenceManager.getProxyPort();
|
||||
}
|
||||
mPreferences.setProxyHost(proxyHost);
|
||||
mPreferences.setProxyPort(proxyPort);
|
||||
mPreferenceManager.setProxyHost(proxyHost);
|
||||
mPreferenceManager.setProxyPort(proxyPort);
|
||||
proxy.setSummary(proxyHost + ':' + proxyPort);
|
||||
}
|
||||
}).show();
|
||||
@@ -292,29 +280,24 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
"DuckDuckGo (Privacy)", "DuckDuckGo Lite (Privacy)", "Baidu (Chinese)",
|
||||
"Yandex (Russian)"};
|
||||
|
||||
int n = mPreferences.getSearchChoice();
|
||||
int n = mPreferenceManager.getSearchChoice();
|
||||
|
||||
picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setSearchChoice(which);
|
||||
mPreferenceManager.setSearchChoice(which);
|
||||
setSearchEngineSummary(which);
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
private void homepageDialog() {
|
||||
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
|
||||
picker.setTitle(getResources().getString(R.string.home));
|
||||
mHomepage = mPreferences.getHomepage();
|
||||
mHomepage = mPreferenceManager.getHomepage();
|
||||
int n;
|
||||
if (mHomepage.contains("about:home")) {
|
||||
n = 1;
|
||||
@@ -332,15 +315,15 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which + 1) {
|
||||
case 1:
|
||||
mPreferences.setHomepage("about:home");
|
||||
mPreferenceManager.setHomepage("about:home");
|
||||
home.setSummary(getResources().getString(R.string.action_homepage));
|
||||
break;
|
||||
case 2:
|
||||
mPreferences.setHomepage("about:blank");
|
||||
mPreferenceManager.setHomepage("about:blank");
|
||||
home.setSummary(getResources().getString(R.string.action_blank));
|
||||
break;
|
||||
case 3:
|
||||
mPreferences.setHomepage("about:bookmarks");
|
||||
mPreferenceManager.setHomepage("about:bookmarks");
|
||||
home.setSummary(getResources().getString(R.string.action_bookmarks));
|
||||
break;
|
||||
case 4:
|
||||
@@ -349,12 +332,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
@@ -362,11 +340,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
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();
|
||||
mHomepage = mPreferenceManager.getHomepage();
|
||||
if (!mHomepage.startsWith("about:")) {
|
||||
getHome.setText(mHomepage);
|
||||
} else {
|
||||
getHome.setText("http://www.google.com");
|
||||
String defaultUrl = "https://www.google.com";
|
||||
getHome.setText(defaultUrl);
|
||||
}
|
||||
homePicker.setView(getHome);
|
||||
homePicker.setPositiveButton(getResources().getString(R.string.action_ok),
|
||||
@@ -374,7 +353,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String text = getHome.getText().toString();
|
||||
mPreferences.setHomepage(text);
|
||||
mPreferenceManager.setHomepage(text);
|
||||
home.setSummary(text);
|
||||
}
|
||||
});
|
||||
@@ -384,48 +363,42 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
private void downloadLocDialog() {
|
||||
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
|
||||
picker.setTitle(getResources().getString(R.string.title_download_location));
|
||||
mDownloadLocation = mPreferences.getDownloadDirectory();
|
||||
mDownloadLocation = mPreferenceManager.getDownloadDirectory();
|
||||
int n;
|
||||
if (mDownloadLocation.contains(Environment.DIRECTORY_DOWNLOADS)) {
|
||||
n = 1;
|
||||
n = 0;
|
||||
} else {
|
||||
n = 2;
|
||||
n = 1;
|
||||
}
|
||||
|
||||
picker.setSingleChoiceItems(R.array.download_folder, n - 1,
|
||||
picker.setSingleChoiceItems(R.array.download_folder, n,
|
||||
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);
|
||||
switch (which) {
|
||||
case 0:
|
||||
mPreferenceManager.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
downloadloc.setSummary(DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
downPicker();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
picker.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
picker.show();
|
||||
}
|
||||
|
||||
private void agentDialog() {
|
||||
AlertDialog.Builder agentPicker = new AlertDialog.Builder(mActivity);
|
||||
agentPicker.setTitle(getResources().getString(R.string.title_user_agent));
|
||||
mAgentChoice = mPreferences.getUserAgentChoice();
|
||||
mAgentChoice = mPreferenceManager.getUserAgentChoice();
|
||||
agentPicker.setSingleChoiceItems(R.array.user_agent, mAgentChoice - 1,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setUserAgentChoice(which + 1);
|
||||
mPreferenceManager.setUserAgentChoice(which + 1);
|
||||
switch (which + 1) {
|
||||
case 1:
|
||||
useragent.setSummary(getResources().getString(R.string.agent_default));
|
||||
@@ -443,18 +416,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
}
|
||||
});
|
||||
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.setNeutralButton(getResources().getString(R.string.action_ok), null);
|
||||
agentPicker.show();
|
||||
}
|
||||
|
||||
@@ -468,7 +430,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String text = getAgent.getText().toString();
|
||||
mPreferences.setUserAgentString(text);
|
||||
mPreferenceManager.setUserAgentString(text);
|
||||
useragent.setSummary(getResources().getString(R.string.agent_custom));
|
||||
}
|
||||
});
|
||||
@@ -480,36 +442,25 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
LinearLayout layout = new LinearLayout(mActivity);
|
||||
downLocationPicker.setTitle(getResources().getString(R.string.title_download_location));
|
||||
final EditText getDownload = new EditText(mActivity);
|
||||
getDownload.setText(mPreferences.getDownloadDirectory());
|
||||
getDownload.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
getDownload.setText(mPreferenceManager.getDownloadDirectory());
|
||||
final int errorColor = ContextCompat.getColor(getActivity(), R.color.error_red);
|
||||
final int regularColor = ThemeUtils.getTextColor(getActivity());
|
||||
getDownload.setTextColor(regularColor);
|
||||
getDownload.addTextChangedListener(new DownloadLocationTextWatcher(getDownload, errorColor, regularColor));
|
||||
getDownload.setText(mPreferenceManager.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);
|
||||
text = DownloadHandler.addNecessarySlashes(text);
|
||||
mPreferenceManager.setDownloadDirectory(text);
|
||||
downloadloc.setSummary(text);
|
||||
}
|
||||
});
|
||||
downLocationPicker.show();
|
||||
@@ -553,7 +504,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_PROXY:
|
||||
proxyChoicePicker();
|
||||
@@ -576,47 +527,72 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
// switch preferences
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
boolean checked = false;
|
||||
if (newValue instanceof Boolean) {
|
||||
checked = (Boolean) newValue;
|
||||
}
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_FLASH:
|
||||
if (cbFlash.isChecked()) {
|
||||
getFlashChoice();
|
||||
} else {
|
||||
mPreferences.setFlashSupport(0);
|
||||
}
|
||||
if (!Utils.isFlashInstalled(mActivity) && cbFlash.isChecked()) {
|
||||
if (!Utils.isFlashInstalled(mActivity) && checked) {
|
||||
Utils.createInformativeDialog(mActivity, R.string.title_warning, R.string.dialog_adobe_not_installed);
|
||||
cbFlash.setEnabled(false);
|
||||
mPreferences.setFlashSupport(0);
|
||||
mPreferenceManager.setFlashSupport(0);
|
||||
return false;
|
||||
} else {
|
||||
if (checked) {
|
||||
getFlashChoice();
|
||||
} else {
|
||||
mPreferenceManager.setFlashSupport(0);
|
||||
}
|
||||
}
|
||||
cbFlash.setChecked((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_ADS:
|
||||
mPreferences.setAdBlockEnabled((Boolean) newValue);
|
||||
cbAds.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setAdBlockEnabled(checked);
|
||||
return true;
|
||||
case SETTINGS_IMAGES:
|
||||
mPreferences.setBlockImagesEnabled((Boolean) newValue);
|
||||
cbImages.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setBlockImagesEnabled(checked);
|
||||
return true;
|
||||
case SETTINGS_JAVASCRIPT:
|
||||
mPreferences.setJavaScriptEnabled((Boolean) newValue);
|
||||
cbJsScript.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setJavaScriptEnabled(checked);
|
||||
return true;
|
||||
case SETTINGS_COLORMODE:
|
||||
mPreferences.setColorModeEnabled((Boolean) newValue);
|
||||
cbColorMode.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setColorModeEnabled(checked);
|
||||
return true;
|
||||
case SETTINGS_GOOGLESUGGESTIONS:
|
||||
mPreferences.setGoogleSearchSuggestionsEnabled((Boolean) newValue);
|
||||
cbgooglesuggest.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setGoogleSearchSuggestionsEnabled(checked);
|
||||
return true;
|
||||
case SETTINGS_DRAWERTABS:
|
||||
mPreferenceManager.setShowTabsInDrawer(checked);
|
||||
return true;
|
||||
case SETTINGS_DRAWERTABS:
|
||||
mPreferences.setShowTabsInDrawer((Boolean) newValue);
|
||||
cbDrawerTabs.setChecked((Boolean) newValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DownloadLocationTextWatcher implements TextWatcher {
|
||||
private final EditText getDownload;
|
||||
private final int errorColor;
|
||||
private final int regularColor;
|
||||
|
||||
public DownloadLocationTextWatcher(EditText getDownload, int errorColor, int regularColor) {
|
||||
this.getDownload = getDownload;
|
||||
this.errorColor = errorColor;
|
||||
this.regularColor = regularColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
if (!DownloadHandler.isWriteAccessAvailable(s.toString())) {
|
||||
this.getDownload.setTextColor(this.errorColor);
|
||||
} else {
|
||||
this.getDownload.setTextColor(this.regularColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package acr.browser.lightning.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
|
||||
/**
|
||||
* Simplify {@link PreferenceManager} inject in all the PreferenceFragments
|
||||
*
|
||||
* @author Stefano Pacifici
|
||||
* @date 2015/09/16
|
||||
*/
|
||||
public class LightningPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Inject
|
||||
PreferenceManager mPreferenceManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,20 @@ import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.database.HistoryDatabase;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.utils.WebUtils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public class PrivacySettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
public class PrivacySettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String SETTINGS_LOCATION = "location";
|
||||
private static final String SETTINGS_THIRDPCOOKIES = "third_party";
|
||||
@@ -33,16 +37,18 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
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 static final String SETTINGS_DONOTTRACK = "do_not_track";
|
||||
private static final String SETTINGS_IDENTIFYINGHEADERS = "remove_identifying_headers";
|
||||
|
||||
private Activity mActivity;
|
||||
private PreferenceManager mPreferences;
|
||||
private CheckBoxPreference cblocation, cb3cookies, cbsavepasswords, cbcacheexit, cbhistoryexit,
|
||||
cbcookiesexit, cbwebstorageexit;
|
||||
private Handler messageHandler;
|
||||
private Handler mMessageHandler;
|
||||
|
||||
@Inject HistoryDatabase mHistoryDatabase;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preference_privacy);
|
||||
|
||||
@@ -52,21 +58,20 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
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);
|
||||
CheckBoxPreference cblocation = (CheckBoxPreference) findPreference(SETTINGS_LOCATION);
|
||||
CheckBoxPreference cb3cookies = (CheckBoxPreference) findPreference(SETTINGS_THIRDPCOOKIES);
|
||||
CheckBoxPreference cbsavepasswords = (CheckBoxPreference) findPreference(SETTINGS_SAVEPASSWORD);
|
||||
CheckBoxPreference cbcacheexit = (CheckBoxPreference) findPreference(SETTINGS_CACHEEXIT);
|
||||
CheckBoxPreference cbhistoryexit = (CheckBoxPreference) findPreference(SETTINGS_HISTORYEXIT);
|
||||
CheckBoxPreference cbcookiesexit = (CheckBoxPreference) findPreference(SETTINGS_COOKIEEXIT);
|
||||
CheckBoxPreference cbwebstorageexit = (CheckBoxPreference) findPreference(SETTINGS_WEBSTORAGEEXIT);
|
||||
CheckBoxPreference cbDoNotTrack = (CheckBoxPreference) findPreference(SETTINGS_DONOTTRACK);
|
||||
CheckBoxPreference cbIdentifyingHeaders = (CheckBoxPreference) findPreference(SETTINGS_IDENTIFYINGHEADERS);
|
||||
|
||||
clearcache.setOnPreferenceClickListener(this);
|
||||
clearhistory.setOnPreferenceClickListener(this);
|
||||
@@ -80,18 +85,28 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
cbhistoryexit.setOnPreferenceChangeListener(this);
|
||||
cbcookiesexit.setOnPreferenceChangeListener(this);
|
||||
cbwebstorageexit.setOnPreferenceChangeListener(this);
|
||||
cbDoNotTrack.setOnPreferenceChangeListener(this);
|
||||
cbIdentifyingHeaders.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());
|
||||
cblocation.setChecked(mPreferenceManager.getLocationEnabled());
|
||||
cbsavepasswords.setChecked(mPreferenceManager.getSavePasswordsEnabled());
|
||||
cbcacheexit.setChecked(mPreferenceManager.getClearCacheExit());
|
||||
cbhistoryexit.setChecked(mPreferenceManager.getClearHistoryExitEnabled());
|
||||
cbcookiesexit.setChecked(mPreferenceManager.getClearCookiesExitEnabled());
|
||||
cb3cookies.setChecked(mPreferenceManager.getBlockThirdPartyCookiesEnabled());
|
||||
cbwebstorageexit.setChecked(mPreferenceManager.getClearWebStorageExitEnabled());
|
||||
cbDoNotTrack.setChecked(mPreferenceManager.getDoNotTrackEnabled() && Utils.doesSupportHeaders());
|
||||
cbIdentifyingHeaders.setChecked(mPreferenceManager.getRemoveIdentifyingHeadersEnabled() && Utils.doesSupportHeaders());
|
||||
|
||||
cbDoNotTrack.setEnabled(Utils.doesSupportHeaders());
|
||||
cbIdentifyingHeaders.setEnabled(Utils.doesSupportHeaders());
|
||||
|
||||
String identifyingHeadersSummary = LightningView.HEADER_REQUESTED_WITH + ", " + LightningView.HEADER_WAP_PROFILE;
|
||||
cbIdentifyingHeaders.setSummary(identifyingHeadersSummary);
|
||||
|
||||
cb3cookies.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
|
||||
|
||||
messageHandler = new MessageHandler(mActivity);
|
||||
mMessageHandler = new MessageHandler(mActivity);
|
||||
}
|
||||
|
||||
private static class MessageHandler extends Handler {
|
||||
@@ -103,7 +118,7 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
switch (msg.what) {
|
||||
case 1:
|
||||
Utils.showSnackbar(mHandlerContext, R.string.message_clear_history);
|
||||
@@ -117,7 +132,7 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_CLEARCACHE:
|
||||
clearCache();
|
||||
@@ -144,21 +159,15 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface arg0, int arg1) {
|
||||
Thread clear = new Thread(new Runnable() {
|
||||
BrowserApp.getIOThread().execute(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();
|
||||
.setNegativeButton(getResources().getString(R.string.action_no), null).show();
|
||||
}
|
||||
|
||||
private void clearCookiesDialog() {
|
||||
@@ -169,21 +178,15 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface arg0, int arg1) {
|
||||
Thread clear = new Thread(new Runnable() {
|
||||
BrowserApp.getTaskThread().execute(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();
|
||||
.setNegativeButton(getResources().getString(R.string.action_no), null).show();
|
||||
}
|
||||
|
||||
private void clearCache() {
|
||||
@@ -194,13 +197,13 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
private void clearHistory() {
|
||||
WebUtils.clearHistory(getActivity());
|
||||
messageHandler.sendEmptyMessage(1);
|
||||
WebUtils.clearHistory(getActivity(), mHistoryDatabase);
|
||||
mMessageHandler.sendEmptyMessage(1);
|
||||
}
|
||||
|
||||
private void clearCookies() {
|
||||
WebUtils.clearCookies(getActivity());
|
||||
messageHandler.sendEmptyMessage(2);
|
||||
mMessageHandler.sendEmptyMessage(2);
|
||||
}
|
||||
|
||||
private void clearWebStorage() {
|
||||
@@ -209,36 +212,34 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
// switch preferences
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
switch (preference.getKey()) {
|
||||
case SETTINGS_LOCATION:
|
||||
mPreferences.setLocationEnabled((Boolean) newValue);
|
||||
cblocation.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setLocationEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_THIRDPCOOKIES:
|
||||
mPreferences.setBlockThirdPartyCookiesEnabled((Boolean) newValue);
|
||||
cb3cookies.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setBlockThirdPartyCookiesEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_SAVEPASSWORD:
|
||||
mPreferences.setSavePasswordsEnabled((Boolean) newValue);
|
||||
cbsavepasswords.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setSavePasswordsEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_CACHEEXIT:
|
||||
mPreferences.setClearCacheExit((Boolean) newValue);
|
||||
cbcacheexit.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setClearCacheExit((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_HISTORYEXIT:
|
||||
mPreferences.setClearHistoryExitEnabled((Boolean) newValue);
|
||||
cbhistoryexit.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setClearHistoryExitEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_COOKIEEXIT:
|
||||
mPreferences.setClearCookiesExitEnabled((Boolean) newValue);
|
||||
cbcookiesexit.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setClearCookiesExitEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_WEBSTORAGEEXIT:
|
||||
mPreferences.setClearWebStorageExitEnabled((Boolean) newValue);
|
||||
cbwebstorageexit.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setClearWebStorageExitEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_DONOTTRACK:
|
||||
mPreferenceManager.setDoNotTrackEnabled((Boolean) newValue);
|
||||
return true;
|
||||
case SETTINGS_IDENTIFYINGHEADERS:
|
||||
mPreferenceManager.setRemoveIdentifyingHeadersEnabled((Boolean) newValue);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
package acr.browser.lightning.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.widget.TextViewCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.LayoutManager;
|
||||
import android.support.v7.widget.SimpleItemAnimator;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.TabsManager;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.browser.TabsView;
|
||||
import acr.browser.lightning.bus.NavigationEvents;
|
||||
import acr.browser.lightning.bus.TabEvents;
|
||||
import acr.browser.lightning.controller.UIController;
|
||||
import acr.browser.lightning.fragment.anim.HorizontalItemAnimator;
|
||||
import acr.browser.lightning.fragment.anim.VerticalItemAnimator;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
/**
|
||||
* A fragment that holds and manages the tabs and interaction with the tabs.
|
||||
* It is reliant on the BrowserController in order to get the current UI state
|
||||
* of the browser. It also uses the BrowserController to signal that the UI needs
|
||||
* to change. This class contains the adapter used by both the drawer tabs and
|
||||
* the desktop tabs. It delegates touch events for the tab UI appropriately.
|
||||
*/
|
||||
public class TabsFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener, TabsView {
|
||||
|
||||
private static final String TAG = TabsFragment.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Arguments boolean to tell the fragment it is displayed in the drawner or on the tab strip
|
||||
* If true, the fragment is in the left drawner in the strip otherwise.
|
||||
*/
|
||||
public static final String VERTICAL_MODE = TAG + ".VERTICAL_MODE";
|
||||
public static final String IS_INCOGNITO = TAG + ".IS_INCOGNITO";
|
||||
|
||||
private boolean mIsIncognito, mDarkTheme;
|
||||
private int mIconColor;
|
||||
private boolean mColorMode = true;
|
||||
private boolean mShowInNavigationDrawer;
|
||||
|
||||
@Nullable private LightningViewAdapter mTabsAdapter;
|
||||
private UIController mUiController;
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
private TabsManager mTabsManager;
|
||||
@Inject Bus mBus;
|
||||
@Inject PreferenceManager mPreferences;
|
||||
|
||||
public TabsFragment() {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Bundle arguments = getArguments();
|
||||
final Context context = getContext();
|
||||
mUiController = (UIController) getActivity();
|
||||
mTabsManager = mUiController.getTabModel();
|
||||
mIsIncognito = arguments.getBoolean(IS_INCOGNITO, false);
|
||||
mShowInNavigationDrawer = arguments.getBoolean(VERTICAL_MODE, true);
|
||||
mDarkTheme = mPreferences.getUseTheme() != 0 || mIsIncognito;
|
||||
mColorMode = mPreferences.getColorModeEnabled();
|
||||
mColorMode &= !mDarkTheme;
|
||||
mIconColor = mDarkTheme ?
|
||||
ThemeUtils.getIconDarkThemeColor(context) :
|
||||
ThemeUtils.getIconLightThemeColor(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view;
|
||||
final LayoutManager layoutManager;
|
||||
if (mShowInNavigationDrawer) {
|
||||
view = inflater.inflate(R.layout.tab_drawer, container, false);
|
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
|
||||
setupFrameLayoutButton(view, R.id.tab_header_button, R.id.plusIcon);
|
||||
setupFrameLayoutButton(view, R.id.new_tab_button, R.id.icon_plus);
|
||||
setupFrameLayoutButton(view, R.id.action_back, R.id.icon_back);
|
||||
setupFrameLayoutButton(view, R.id.action_forward, R.id.icon_forward);
|
||||
setupFrameLayoutButton(view, R.id.action_home, R.id.icon_home);
|
||||
} else {
|
||||
view = inflater.inflate(R.layout.tab_strip, container, false);
|
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
|
||||
ImageView newTab = (ImageView) view.findViewById(R.id.new_tab_button);
|
||||
newTab.setColorFilter(ThemeUtils.getIconDarkThemeColor(getActivity()));
|
||||
newTab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mUiController.newTabClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
mRecyclerView = (RecyclerView) view.findViewById(R.id.tabs_list);
|
||||
SimpleItemAnimator animator;
|
||||
if (mShowInNavigationDrawer) {
|
||||
animator = new VerticalItemAnimator();
|
||||
} else {
|
||||
animator = new HorizontalItemAnimator();
|
||||
}
|
||||
animator.setSupportsChangeAnimations(false);
|
||||
animator.setAddDuration(200);
|
||||
animator.setChangeDuration(0);
|
||||
animator.setRemoveDuration(200);
|
||||
animator.setMoveDuration(200);
|
||||
mRecyclerView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mRecyclerView.setItemAnimator(animator);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
mTabsAdapter = new LightningViewAdapter(mShowInNavigationDrawer);
|
||||
mRecyclerView.setAdapter(mTabsAdapter);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId,
|
||||
@IdRes final int imageId) {
|
||||
final View frameButton = root.findViewById(buttonId);
|
||||
final ImageView buttonImage = (ImageView) root.findViewById(imageId);
|
||||
frameButton.setOnClickListener(this);
|
||||
frameButton.setOnLongClickListener(this);
|
||||
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mTabsAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Force adapter refresh
|
||||
if (mTabsAdapter != null) {
|
||||
mTabsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
mBus.unregister(this);
|
||||
}
|
||||
|
||||
public void reinitializePreferences() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
mDarkTheme = mPreferences.getUseTheme() != 0 || mIsIncognito;
|
||||
mColorMode = mPreferences.getColorModeEnabled();
|
||||
mColorMode &= !mDarkTheme;
|
||||
mIconColor = mDarkTheme ?
|
||||
ThemeUtils.getIconDarkThemeColor(activity) :
|
||||
ThemeUtils.getIconLightThemeColor(activity);
|
||||
if (mTabsAdapter != null) {
|
||||
mTabsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NonNull View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.tab_header_button:
|
||||
mUiController.showCloseDialog(mTabsManager.indexOfCurrentTab());
|
||||
break;
|
||||
case R.id.new_tab_button:
|
||||
mBus.post(new TabEvents.NewTab());
|
||||
break;
|
||||
case R.id.action_back:
|
||||
mBus.post(new NavigationEvents.GoBack());
|
||||
break;
|
||||
case R.id.action_forward:
|
||||
mBus.post(new NavigationEvents.GoForward());
|
||||
break;
|
||||
case R.id.action_home:
|
||||
mBus.post(new NavigationEvents.GoHome());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(@NonNull View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.action_new_tab:
|
||||
mBus.post(new TabEvents.NewTabLongPress());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tabAdded() {
|
||||
if (mTabsAdapter != null) {
|
||||
mTabsAdapter.notifyItemInserted(mTabsManager.last());
|
||||
mRecyclerView.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mRecyclerView.smoothScrollToPosition(mTabsAdapter.getItemCount() - 1);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tabRemoved(int position) {
|
||||
if (mTabsAdapter != null) {
|
||||
mTabsAdapter.notifyItemRemoved(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tabChanged(int position) {
|
||||
if (mTabsAdapter != null) {
|
||||
mTabsAdapter.notifyItemChanged(position);
|
||||
}
|
||||
}
|
||||
|
||||
private class LightningViewAdapter extends RecyclerView.Adapter<LightningViewAdapter.LightningViewHolder> {
|
||||
|
||||
private final int mLayoutResourceId;
|
||||
@Nullable private final Drawable mBackgroundTabDrawable;
|
||||
@Nullable private final Drawable mForegroundTabDrawable;
|
||||
@Nullable private final Bitmap mForegroundTabBitmap;
|
||||
private ColorMatrix mColorMatrix;
|
||||
private Paint mPaint;
|
||||
private ColorFilter mFilter;
|
||||
private static final float DESATURATED = 0.5f;
|
||||
|
||||
private final boolean mDrawerTabs;
|
||||
|
||||
public LightningViewAdapter(final boolean vertical) {
|
||||
this.mLayoutResourceId = vertical ? R.layout.tab_list_item : R.layout.tab_list_item_horizontal;
|
||||
this.mDrawerTabs = vertical;
|
||||
|
||||
if (vertical) {
|
||||
mBackgroundTabDrawable = null;
|
||||
mForegroundTabBitmap = null;
|
||||
mForegroundTabDrawable = ThemeUtils.getSelectedBackground(getContext(), mDarkTheme);
|
||||
} else {
|
||||
int backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(getContext()), Color.BLACK, 0.75f);
|
||||
Bitmap backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888);
|
||||
Utils.drawTrapezoid(new Canvas(backgroundTabBitmap), backgroundColor, true);
|
||||
mBackgroundTabDrawable = new BitmapDrawable(getResources(), backgroundTabBitmap);
|
||||
|
||||
int foregroundColor = ThemeUtils.getPrimaryColor(getContext());
|
||||
mForegroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888);
|
||||
Utils.drawTrapezoid(new Canvas(mForegroundTabBitmap), foregroundColor, false);
|
||||
mForegroundTabDrawable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public LightningViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
||||
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
|
||||
View view = inflater.inflate(mLayoutResourceId, viewGroup, false);
|
||||
return new LightningViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final LightningViewHolder holder, int position) {
|
||||
holder.exitButton.setTag(position);
|
||||
|
||||
ViewCompat.jumpDrawablesToCurrentState(holder.exitButton);
|
||||
|
||||
LightningView web = mTabsManager.getTabAtPosition(position);
|
||||
if (web == null) {
|
||||
return;
|
||||
}
|
||||
holder.txtTitle.setText(web.getTitle());
|
||||
|
||||
final Bitmap favicon = web.getFavicon();
|
||||
if (web.isForegroundTab()) {
|
||||
TextViewCompat.setTextAppearance(holder.txtTitle, R.style.boldText);
|
||||
Drawable foregroundDrawable;
|
||||
if (!mDrawerTabs) {
|
||||
foregroundDrawable = new BitmapDrawable(getResources(), mForegroundTabBitmap);
|
||||
if (!mIsIncognito && mColorMode) {
|
||||
foregroundDrawable.setColorFilter(mUiController.getUiColor(), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
} else {
|
||||
foregroundDrawable = mForegroundTabDrawable;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
holder.layout.setBackground(foregroundDrawable);
|
||||
} else {
|
||||
holder.layout.setBackgroundDrawable(foregroundDrawable);
|
||||
}
|
||||
if (!mIsIncognito && mColorMode) {
|
||||
mUiController.changeToolbarBackground(favicon, foregroundDrawable);
|
||||
}
|
||||
holder.favicon.setImageBitmap(favicon);
|
||||
} else {
|
||||
TextViewCompat.setTextAppearance(holder.txtTitle, R.style.normalText);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
holder.layout.setBackground(mBackgroundTabDrawable);
|
||||
} else {
|
||||
holder.layout.setBackgroundDrawable(mBackgroundTabDrawable);
|
||||
}
|
||||
holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mTabsManager.size();
|
||||
}
|
||||
|
||||
public Bitmap getDesaturatedBitmap(@NonNull Bitmap favicon) {
|
||||
Bitmap grayscaleBitmap = Bitmap.createBitmap(favicon.getWidth(),
|
||||
favicon.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
|
||||
Canvas c = new Canvas(grayscaleBitmap);
|
||||
if (mColorMatrix == null || mFilter == null || mPaint == null) {
|
||||
mPaint = new Paint();
|
||||
mColorMatrix = new ColorMatrix();
|
||||
mColorMatrix.setSaturation(DESATURATED);
|
||||
mFilter = new ColorMatrixColorFilter(mColorMatrix);
|
||||
mPaint.setColorFilter(mFilter);
|
||||
}
|
||||
|
||||
c.drawBitmap(favicon, 0, 0, mPaint);
|
||||
return grayscaleBitmap;
|
||||
}
|
||||
|
||||
public class LightningViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
public LightningViewHolder(@NonNull View view) {
|
||||
super(view);
|
||||
txtTitle = (TextView) view.findViewById(R.id.textTab);
|
||||
favicon = (ImageView) view.findViewById(R.id.faviconTab);
|
||||
exit = (ImageView) view.findViewById(R.id.deleteButton);
|
||||
layout = (LinearLayout) view.findViewById(R.id.tab_item_background);
|
||||
exitButton = (FrameLayout) view.findViewById(R.id.deleteAction);
|
||||
exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
exitButton.setOnClickListener(this);
|
||||
layout.setOnClickListener(this);
|
||||
layout.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
@NonNull final TextView txtTitle;
|
||||
@NonNull final ImageView favicon;
|
||||
@NonNull final ImageView exit;
|
||||
@NonNull final FrameLayout exitButton;
|
||||
@NonNull final LinearLayout layout;
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == exitButton) {
|
||||
// Close tab
|
||||
mBus.post(new TabEvents.CloseTab(getAdapterPosition()));
|
||||
}
|
||||
if (v == layout) {
|
||||
mBus.post(new TabEvents.ShowTab(getAdapterPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
// Show close dialog
|
||||
mBus.post(new TabEvents.ShowCloseDialog(getAdapterPosition()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package acr.browser.lightning.fragment.anim;
|
||||
|
||||
import android.support.v4.animation.AnimatorCompatHelper;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.support.v7.widget.SimpleItemAnimator;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
|
||||
* animations on remove, add, and move events that happen to the items in
|
||||
* a RecyclerView. RecyclerView uses a HorizontalItemAnimator by default.
|
||||
*
|
||||
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
|
||||
*/
|
||||
public class HorizontalItemAnimator extends SimpleItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
|
||||
|
||||
private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
|
||||
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
|
||||
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
|
||||
|
||||
private ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
|
||||
|
||||
private static class MoveInfo {
|
||||
public ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
this.holder = holder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChangeInfo {
|
||||
public ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this(oldHolder, newHolder);
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{" +
|
||||
"oldHolder=" + oldHolder +
|
||||
", newHolder=" + newHolder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
boolean removalsPending = !mPendingRemovals.isEmpty();
|
||||
boolean movesPending = !mPendingMoves.isEmpty();
|
||||
boolean changesPending = !mPendingChanges.isEmpty();
|
||||
boolean additionsPending = !mPendingAdditions.isEmpty();
|
||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
|
||||
// nothing to animate
|
||||
return;
|
||||
}
|
||||
// First, remove stuff
|
||||
for (ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<>();
|
||||
moves.addAll(mPendingMoves);
|
||||
mMovesList.add(moves);
|
||||
mPendingMoves.clear();
|
||||
Runnable mover = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (MoveInfo moveInfo : moves) {
|
||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
|
||||
moveInfo.toX, moveInfo.toY);
|
||||
}
|
||||
moves.clear();
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
View view = moves.get(0).holder.itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
|
||||
} else {
|
||||
mover.run();
|
||||
}
|
||||
}
|
||||
// Next, change stuff, to run in parallel with move animations
|
||||
if (changesPending) {
|
||||
final ArrayList<ChangeInfo> changes = new ArrayList<>();
|
||||
changes.addAll(mPendingChanges);
|
||||
mChangesList.add(changes);
|
||||
mPendingChanges.clear();
|
||||
Runnable changer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (ChangeInfo change : changes) {
|
||||
animateChangeImpl(change);
|
||||
}
|
||||
changes.clear();
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<ViewHolder> additions = new ArrayList<>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
public void run() {
|
||||
for (ViewHolder holder : additions) {
|
||||
animateAddImpl(holder);
|
||||
}
|
||||
additions.clear();
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
};
|
||||
if (removalsPending || movesPending || changesPending) {
|
||||
long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
View view = additions.get(0).itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
|
||||
} else {
|
||||
adder.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration())
|
||||
.alpha(0).translationY(holder.itemView.getHeight())
|
||||
.setInterpolator(new AccelerateInterpolator()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
ViewCompat.setAlpha(holder.itemView, 0);
|
||||
ViewCompat.setTranslationY(holder.itemView, holder.itemView.getHeight());
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateAddImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).translationY(0)
|
||||
.setInterpolator(new DecelerateInterpolator()).setDuration(getAddDuration())
|
||||
.setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += ViewCompat.getTranslationX(holder.itemView);
|
||||
fromY += ViewCompat.getTranslationY(holder.itemView);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
resetAnimation(holder);
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
// TODO: make EndActions end listeners instead, since end actions aren't called when
|
||||
// vpas are canceled (and can't end them. why?)
|
||||
// need listener functionality in VPACompat for this. Ick.
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
// if (oldHolder != newHolder) {
|
||||
// if (oldHolder != null) {
|
||||
// dispatchChangeFinished(oldHolder, true);
|
||||
// }
|
||||
// if (newHolder != null) {
|
||||
// dispatchChangeFinished(newHolder, false);
|
||||
// }
|
||||
// } else if (oldHolder != null) {
|
||||
// dispatchChangeFinished(oldHolder, true);
|
||||
// }
|
||||
// return false;
|
||||
if (oldHolder == newHolder) {
|
||||
// Don't know how to run change animations when the same view holder is re-used.
|
||||
// run a move animation to handle position changes.
|
||||
if ((fromX - toX) == 0 && (fromY - toY) == 0) {
|
||||
dispatchMoveFinished(oldHolder);
|
||||
return false;
|
||||
}
|
||||
return animateMove(oldHolder, fromX, fromY, toX, toY);
|
||||
}
|
||||
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
|
||||
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
|
||||
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
|
||||
resetAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
|
||||
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
|
||||
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
|
||||
if (newHolder != null) {
|
||||
// carry over translation values
|
||||
resetAnimation(newHolder);
|
||||
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
|
||||
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
|
||||
ViewCompat.setAlpha(newHolder.itemView, 0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
oldViewAnim.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
|
||||
alpha(1).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
newViewAnimation.setListener(null);
|
||||
ViewCompat.setAlpha(newView, 1);
|
||||
ViewCompat.setTranslationX(newView, 0);
|
||||
ViewCompat.setTranslationY(newView, 0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
|
||||
for (int i = infoList.size() - 1; i >= 0; i--) {
|
||||
ChangeInfo changeInfo = infoList.get(i);
|
||||
if (endChangeAnimationIfNecessary(changeInfo, item)) {
|
||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
|
||||
infoList.remove(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
|
||||
if (changeInfo.oldHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
|
||||
}
|
||||
if (changeInfo.newHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
|
||||
boolean oldItem = false;
|
||||
if (changeInfo.newHolder == item) {
|
||||
changeInfo.newHolder = null;
|
||||
} else if (changeInfo.oldHolder == item) {
|
||||
changeInfo.oldHolder = null;
|
||||
oldItem = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
ViewCompat.setTranslationX(item.itemView, 0);
|
||||
ViewCompat.setTranslationY(item.itemView, 0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
ViewCompat.animate(view).cancel();
|
||||
// TODO if some other animations are chained to end, how do we cancel them as well?
|
||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
|
||||
MoveInfo moveInfo = mPendingMoves.get(i);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
for (int i = mChangesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
endChangeAnimation(changes, item);
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(i);
|
||||
}
|
||||
}
|
||||
for (int i = mMovesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
for (int j = moves.size() - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
private void resetAnimation(ViewHolder holder) {
|
||||
AnimatorCompatHelper.clearInterpolator(holder.itemView);
|
||||
endAnimation(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!mPendingAdditions.isEmpty() ||
|
||||
!mPendingChanges.isEmpty() ||
|
||||
!mPendingMoves.isEmpty() ||
|
||||
!mPendingRemovals.isEmpty() ||
|
||||
!mMoveAnimations.isEmpty() ||
|
||||
!mRemoveAnimations.isEmpty() ||
|
||||
!mAddAnimations.isEmpty() ||
|
||||
!mChangeAnimations.isEmpty() ||
|
||||
!mMovesList.isEmpty() ||
|
||||
!mAdditionsList.isEmpty() ||
|
||||
!mChangesList.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of currently pending and running animations. If there are none
|
||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
|
||||
* listeners.
|
||||
*/
|
||||
private void dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
int count = mPendingMoves.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
MoveInfo item = mPendingMoves.get(i);
|
||||
View view = item.holder.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingAdditions.get(i);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
mPendingAdditions.remove(i);
|
||||
}
|
||||
count = mPendingChanges.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
endChangeAnimationIfNecessary(mPendingChanges.get(i));
|
||||
}
|
||||
mPendingChanges.clear();
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int listCount = mMovesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
count = moves.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(moveInfo.holder);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mAdditionsList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
additions.remove(j);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mChangesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
count = changes.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
endChangeAnimationIfNecessary(changes.get(j));
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll(mRemoveAnimations);
|
||||
cancelAll(mMoveAnimations);
|
||||
cancelAll(mAddAnimations);
|
||||
cancelAll(mChangeAnimations);
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
static void cancelAll(List<ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package acr.browser.lightning.fragment.anim;
|
||||
|
||||
import android.support.v4.animation.AnimatorCompatHelper;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.support.v7.widget.SimpleItemAnimator;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
|
||||
* animations on remove, add, and move events that happen to the items in
|
||||
* a RecyclerView. RecyclerView uses a VerticalItemAnimator by default.
|
||||
*
|
||||
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
|
||||
*/
|
||||
public class VerticalItemAnimator extends SimpleItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
|
||||
|
||||
private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
|
||||
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
|
||||
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
|
||||
|
||||
private ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
|
||||
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
|
||||
|
||||
private static class MoveInfo {
|
||||
public ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
this.holder = holder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChangeInfo {
|
||||
public ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this(oldHolder, newHolder);
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{" +
|
||||
"oldHolder=" + oldHolder +
|
||||
", newHolder=" + newHolder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
boolean removalsPending = !mPendingRemovals.isEmpty();
|
||||
boolean movesPending = !mPendingMoves.isEmpty();
|
||||
boolean changesPending = !mPendingChanges.isEmpty();
|
||||
boolean additionsPending = !mPendingAdditions.isEmpty();
|
||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
|
||||
// nothing to animate
|
||||
return;
|
||||
}
|
||||
// First, remove stuff
|
||||
for (ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<>();
|
||||
moves.addAll(mPendingMoves);
|
||||
mMovesList.add(moves);
|
||||
mPendingMoves.clear();
|
||||
Runnable mover = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (MoveInfo moveInfo : moves) {
|
||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
|
||||
moveInfo.toX, moveInfo.toY);
|
||||
}
|
||||
moves.clear();
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
View view = moves.get(0).holder.itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
|
||||
} else {
|
||||
mover.run();
|
||||
}
|
||||
}
|
||||
// Next, change stuff, to run in parallel with move animations
|
||||
if (changesPending) {
|
||||
final ArrayList<ChangeInfo> changes = new ArrayList<>();
|
||||
changes.addAll(mPendingChanges);
|
||||
mChangesList.add(changes);
|
||||
mPendingChanges.clear();
|
||||
Runnable changer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (ChangeInfo change : changes) {
|
||||
animateChangeImpl(change);
|
||||
}
|
||||
changes.clear();
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<ViewHolder> additions = new ArrayList<>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
public void run() {
|
||||
for (ViewHolder holder : additions) {
|
||||
animateAddImpl(holder);
|
||||
}
|
||||
additions.clear();
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
};
|
||||
if (removalsPending || movesPending || changesPending) {
|
||||
long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
View view = additions.get(0).itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
|
||||
} else {
|
||||
adder.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration())
|
||||
.alpha(0).translationX(-holder.itemView.getWidth() / 2)
|
||||
.setInterpolator(new AccelerateInterpolator()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
ViewCompat.setAlpha(holder.itemView, 0);
|
||||
ViewCompat.setTranslationX(holder.itemView, -holder.itemView.getWidth() / 2);
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateAddImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).translationX(0).setDuration(getAddDuration())
|
||||
.setInterpolator(new DecelerateInterpolator()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += ViewCompat.getTranslationX(holder.itemView);
|
||||
fromY += ViewCompat.getTranslationY(holder.itemView);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
resetAnimation(holder);
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
// TODO: make EndActions end listeners instead, since end actions aren't called when
|
||||
// vpas are canceled (and can't end them. why?)
|
||||
// need listener functionality in VPACompat for this. Ick.
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
// if (oldHolder != newHolder) {
|
||||
// if (oldHolder != null) {
|
||||
// dispatchChangeFinished(oldHolder, true);
|
||||
// }
|
||||
// if (newHolder != null) {
|
||||
// dispatchChangeFinished(newHolder, false);
|
||||
// }
|
||||
// } else if (oldHolder != null) {
|
||||
// dispatchChangeFinished(oldHolder, true);
|
||||
// }
|
||||
// return false;
|
||||
if (oldHolder == newHolder) {
|
||||
// Don't know how to run change animations when the same view holder is re-used.
|
||||
// run a move animation to handle position changes.
|
||||
if ((fromX - toX) == 0 && (fromY - toY) == 0) {
|
||||
dispatchMoveFinished(oldHolder);
|
||||
return false;
|
||||
}
|
||||
return animateMove(oldHolder, fromX, fromY, toX, toY);
|
||||
}
|
||||
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
|
||||
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
|
||||
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
|
||||
resetAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
|
||||
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
|
||||
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
|
||||
if (newHolder != null) {
|
||||
// carry over translation values
|
||||
resetAnimation(newHolder);
|
||||
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
|
||||
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
|
||||
ViewCompat.setAlpha(newHolder.itemView, 0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
oldViewAnim.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
|
||||
alpha(1).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
newViewAnimation.setListener(null);
|
||||
ViewCompat.setAlpha(newView, 1);
|
||||
ViewCompat.setTranslationX(newView, 0);
|
||||
ViewCompat.setTranslationY(newView, 0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
|
||||
for (int i = infoList.size() - 1; i >= 0; i--) {
|
||||
ChangeInfo changeInfo = infoList.get(i);
|
||||
if (endChangeAnimationIfNecessary(changeInfo, item)) {
|
||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
|
||||
infoList.remove(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
|
||||
if (changeInfo.oldHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
|
||||
}
|
||||
if (changeInfo.newHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
|
||||
boolean oldItem = false;
|
||||
if (changeInfo.newHolder == item) {
|
||||
changeInfo.newHolder = null;
|
||||
} else if (changeInfo.oldHolder == item) {
|
||||
changeInfo.oldHolder = null;
|
||||
oldItem = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
ViewCompat.setTranslationX(item.itemView, 0);
|
||||
ViewCompat.setTranslationY(item.itemView, 0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
ViewCompat.animate(view).cancel();
|
||||
// TODO if some other animations are chained to end, how do we cancel them as well?
|
||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
|
||||
MoveInfo moveInfo = mPendingMoves.get(i);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
for (int i = mChangesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
endChangeAnimation(changes, item);
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(i);
|
||||
}
|
||||
}
|
||||
for (int i = mMovesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
for (int j = moves.size() - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
private void resetAnimation(ViewHolder holder) {
|
||||
AnimatorCompatHelper.clearInterpolator(holder.itemView);
|
||||
endAnimation(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!mPendingAdditions.isEmpty() ||
|
||||
!mPendingChanges.isEmpty() ||
|
||||
!mPendingMoves.isEmpty() ||
|
||||
!mPendingRemovals.isEmpty() ||
|
||||
!mMoveAnimations.isEmpty() ||
|
||||
!mRemoveAnimations.isEmpty() ||
|
||||
!mAddAnimations.isEmpty() ||
|
||||
!mChangeAnimations.isEmpty() ||
|
||||
!mMovesList.isEmpty() ||
|
||||
!mAdditionsList.isEmpty() ||
|
||||
!mChangesList.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of currently pending and running animations. If there are none
|
||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
|
||||
* listeners.
|
||||
*/
|
||||
private void dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
int count = mPendingMoves.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
MoveInfo item = mPendingMoves.get(i);
|
||||
View view = item.holder.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingAdditions.get(i);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
mPendingAdditions.remove(i);
|
||||
}
|
||||
count = mPendingChanges.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
endChangeAnimationIfNecessary(mPendingChanges.get(i));
|
||||
}
|
||||
mPendingChanges.clear();
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int listCount = mMovesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
count = moves.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(moveInfo.holder);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mAdditionsList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
additions.remove(j);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mChangesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
count = changes.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
endChangeAnimationIfNecessary(changes.get(j));
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll(mRemoveAnimations);
|
||||
cancelAll(mMoveAnimations);
|
||||
cancelAll(mAddAnimations);
|
||||
cancelAll(mChangeAnimations);
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
static void cancelAll(List<ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package acr.browser.lightning.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.activity.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.download.DownloadHandler;
|
||||
|
||||
@Singleton
|
||||
public class PreferenceManager {
|
||||
|
||||
private static class Name {
|
||||
@@ -14,7 +20,7 @@ public class PreferenceManager {
|
||||
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 DOWNLOAD_DIRECTORY = "downloadLocation";
|
||||
public static final String FULL_SCREEN = "fullscreen";
|
||||
public static final String HIDE_STATUS_BAR = "hidestatus";
|
||||
public static final String HOMEPAGE = "home";
|
||||
@@ -29,7 +35,6 @@ public class PreferenceManager {
|
||||
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";
|
||||
@@ -44,10 +49,11 @@ public class PreferenceManager {
|
||||
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 DO_NOT_TRACK = "doNotTrack";
|
||||
public static final String IDENTIFYING_HEADERS = "removeIdentifyingHeaders";
|
||||
|
||||
public static final String USE_PROXY = "useProxy";
|
||||
public static final String PROXY_CHOICE = "proxyChoice";
|
||||
@@ -55,22 +61,17 @@ public class PreferenceManager {
|
||||
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";
|
||||
|
||||
public static final String LEAK_CANARY = "leakCanary";
|
||||
}
|
||||
|
||||
private static PreferenceManager mInstance;
|
||||
private SharedPreferences mPrefs;
|
||||
@NonNull private final 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);
|
||||
@Inject
|
||||
PreferenceManager(@NonNull final Context context) {
|
||||
mPrefs = context.getSharedPreferences(PREFERENCES, 0);
|
||||
}
|
||||
|
||||
public boolean getAdBlockEnabled() {
|
||||
@@ -110,19 +111,16 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public boolean getColorModeEnabled() {
|
||||
return mPrefs.getBoolean(Name.ENABLE_COLOR_MODE, false);
|
||||
return mPrefs.getBoolean(Name.ENABLE_COLOR_MODE, true);
|
||||
}
|
||||
|
||||
public boolean getCookiesEnabled() {
|
||||
return mPrefs.getBoolean(Name.COOKIES, true);
|
||||
}
|
||||
|
||||
public boolean getDefaultBookmarks() {
|
||||
return mPrefs.getBoolean(Name.DEFAULT_BOOKMARKS, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getDownloadDirectory() {
|
||||
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, Environment.DIRECTORY_DOWNLOADS);
|
||||
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
}
|
||||
|
||||
public int getFlashSupport() {
|
||||
@@ -141,6 +139,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.HIDE_STATUS_BAR, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getHomepage() {
|
||||
return mPrefs.getString(Name.HOMEPAGE, Constants.HOMEPAGE);
|
||||
}
|
||||
@@ -161,10 +160,6 @@ public class PreferenceManager {
|
||||
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);
|
||||
}
|
||||
@@ -173,6 +168,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.POPUPS, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getProxyHost() {
|
||||
return mPrefs.getString(Name.USE_PROXY_HOST, "localhost");
|
||||
}
|
||||
@@ -193,6 +189,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.RESTORE_LOST_TABS, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSavedUrl() {
|
||||
return mPrefs.getString(Name.SAVE_URL, null);
|
||||
}
|
||||
@@ -205,6 +202,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getInt(Name.SEARCH, 1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getSearchUrl() {
|
||||
return mPrefs.getString(Name.SEARCH_URL, Constants.GOOGLE_SEARCH);
|
||||
}
|
||||
@@ -237,7 +235,8 @@ public class PreferenceManager {
|
||||
return mPrefs.getInt(Name.USER_AGENT, 1);
|
||||
}
|
||||
|
||||
public String getUserAgentString(String def) {
|
||||
@Nullable
|
||||
public String getUserAgentString(@Nullable String def) {
|
||||
return mPrefs.getString(Name.USER_AGENT_STRING, def);
|
||||
}
|
||||
|
||||
@@ -245,31 +244,48 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.USE_WIDE_VIEWPORT, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getTextEncoding() {
|
||||
return mPrefs.getString(Name.TEXT_ENCODING, Constants.DEFAULT_ENCODING);
|
||||
}
|
||||
|
||||
public boolean getShowTabsInDrawer(boolean defaultValue){
|
||||
public boolean getShowTabsInDrawer(boolean defaultValue) {
|
||||
return mPrefs.getBoolean(Name.SHOW_TABS_IN_DRAWER, defaultValue);
|
||||
}
|
||||
|
||||
private void putBoolean(String name, boolean value) {
|
||||
public boolean getDoNotTrackEnabled() {
|
||||
return mPrefs.getBoolean(Name.DO_NOT_TRACK, false);
|
||||
}
|
||||
|
||||
public boolean getRemoveIdentifyingHeadersEnabled() {
|
||||
return mPrefs.getBoolean(Name.IDENTIFYING_HEADERS, false);
|
||||
}
|
||||
|
||||
private void putBoolean(@NonNull String name, boolean value) {
|
||||
mPrefs.edit().putBoolean(name, value).apply();
|
||||
}
|
||||
|
||||
private void putInt(String name, int value) {
|
||||
private void putInt(@NonNull String name, int value) {
|
||||
mPrefs.edit().putInt(name, value).apply();
|
||||
}
|
||||
|
||||
private void putString(String name, String value) {
|
||||
private void putString(@NonNull String name, @Nullable String value) {
|
||||
mPrefs.edit().putString(name, value).apply();
|
||||
}
|
||||
|
||||
public void setShowTabsInDrawer(boolean show){
|
||||
public void setRemoveIdentifyingHeadersEnabled(boolean enabled) {
|
||||
putBoolean(Name.IDENTIFYING_HEADERS, enabled);
|
||||
}
|
||||
|
||||
public void setDoNotTrackEnabled(boolean doNotTrack) {
|
||||
putBoolean(Name.DO_NOT_TRACK, doNotTrack);
|
||||
}
|
||||
|
||||
public void setShowTabsInDrawer(boolean show) {
|
||||
putBoolean(Name.SHOW_TABS_IN_DRAWER, show);
|
||||
}
|
||||
|
||||
public void setTextEncoding(String encoding) {
|
||||
public void setTextEncoding(@NonNull String encoding) {
|
||||
putString(Name.TEXT_ENCODING, encoding);
|
||||
}
|
||||
|
||||
@@ -317,11 +333,7 @@ public class PreferenceManager {
|
||||
putBoolean(Name.COOKIES, enable);
|
||||
}
|
||||
|
||||
public void setDefaultBookmarks(boolean show) {
|
||||
putBoolean(Name.DEFAULT_BOOKMARKS, show);
|
||||
}
|
||||
|
||||
public void setDownloadDirectory(String directory) {
|
||||
public void setDownloadDirectory(@NonNull String directory) {
|
||||
putString(Name.DOWNLOAD_DIRECTORY, directory);
|
||||
}
|
||||
|
||||
@@ -341,7 +353,7 @@ public class PreferenceManager {
|
||||
putBoolean(Name.HIDE_STATUS_BAR, enable);
|
||||
}
|
||||
|
||||
public void setHomepage(String homepage) {
|
||||
public void setHomepage(@NonNull String homepage) {
|
||||
putString(Name.HOMEPAGE, homepage);
|
||||
}
|
||||
|
||||
@@ -361,10 +373,6 @@ public class PreferenceManager {
|
||||
putBoolean(Name.LOCATION, enable);
|
||||
}
|
||||
|
||||
public void setMemoryUrl(String url) {
|
||||
putString(Name.URL_MEMORY, url);
|
||||
}
|
||||
|
||||
public void setOverviewModeEnabled(boolean enable) {
|
||||
putBoolean(Name.OVERVIEW_MODE, enable);
|
||||
}
|
||||
@@ -385,7 +393,7 @@ public class PreferenceManager {
|
||||
putBoolean(Name.RESTORE_LOST_TABS, enable);
|
||||
}
|
||||
|
||||
public void setSavedUrl(String url) {
|
||||
public void setSavedUrl(@Nullable String url) {
|
||||
putString(Name.SAVE_URL, url);
|
||||
}
|
||||
|
||||
@@ -397,7 +405,7 @@ public class PreferenceManager {
|
||||
putInt(Name.SEARCH, choice);
|
||||
}
|
||||
|
||||
public void setSearchUrl(String url) {
|
||||
public void setSearchUrl(@NonNull String url) {
|
||||
putString(Name.SEARCH_URL, url);
|
||||
}
|
||||
|
||||
@@ -417,6 +425,14 @@ public class PreferenceManager {
|
||||
putInt(Name.THEME, theme);
|
||||
}
|
||||
|
||||
public void setUseLeakCanary(boolean useLeakCanary) {
|
||||
putBoolean(Name.LEAK_CANARY, useLeakCanary);
|
||||
}
|
||||
|
||||
public boolean getUseLeakCanary() {
|
||||
return mPrefs.getBoolean(Name.LEAK_CANARY, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid choices:
|
||||
* <ul>
|
||||
@@ -432,7 +448,7 @@ public class PreferenceManager {
|
||||
putInt(Name.PROXY_CHOICE, choice);
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
public void setProxyHost(@NonNull String proxyHost) {
|
||||
putString(Name.USE_PROXY_HOST, proxyHost);
|
||||
}
|
||||
|
||||
@@ -444,7 +460,7 @@ public class PreferenceManager {
|
||||
putInt(Name.USER_AGENT, choice);
|
||||
}
|
||||
|
||||
public void setUserAgentString(String agent) {
|
||||
public void setUserAgentString(@Nullable String agent) {
|
||||
putString(Name.USER_AGENT_STRING, agent);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public interface Action<T> {
|
||||
/**
|
||||
* Should be overridden to send the subscriber
|
||||
* events such as {@link Subscriber#onNext(Object)}
|
||||
* or {@link Subscriber#onComplete()}.
|
||||
*
|
||||
* @param subscriber the subscriber that is sent in
|
||||
* when the user of the Observable
|
||||
* subscribes.
|
||||
*/
|
||||
void onSubscribe(@NonNull Subscriber<T> subscriber);
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import acr.browser.lightning.utils.Preconditions;
|
||||
|
||||
/**
|
||||
* An RxJava implementation. This class allows work
|
||||
* to be done on a certain thread and then allows
|
||||
* items to be emitted on a different thread. It is
|
||||
* a replacement for {@link android.os.AsyncTask}.
|
||||
*
|
||||
* @param <T> the type that the Observable will emit.
|
||||
*/
|
||||
public class Observable<T> {
|
||||
|
||||
private static final String TAG = Observable.class.getSimpleName();
|
||||
|
||||
@NonNull private final Action<T> mAction;
|
||||
@Nullable private Executor mSubscriberThread;
|
||||
@Nullable private Executor mObserverThread;
|
||||
@NonNull private final Executor mDefault;
|
||||
|
||||
private Observable(@NonNull Action<T> action) {
|
||||
mAction = action;
|
||||
Looper looper = Looper.myLooper();
|
||||
Preconditions.checkNonNull(looper);
|
||||
mDefault = new ThreadExecutor(looper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static creator method that creates an Observable from the
|
||||
* {@link Action} that is passed in as the parameter. Action
|
||||
* must not be null.
|
||||
*
|
||||
* @param action the Action to perform
|
||||
* @param <T> the type that will be emitted to the onSubscribe
|
||||
* @return a valid non-null Observable.
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> Observable<T> create(@NonNull Action<T> action) {
|
||||
Preconditions.checkNonNull(action);
|
||||
return new Observable<>(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the Observable what Executor that the onSubscribe
|
||||
* work should run on.
|
||||
*
|
||||
* @param subscribeExecutor the Executor to run the work on.
|
||||
* @return returns this so that calls can be conveniently chained.
|
||||
*/
|
||||
public Observable<T> subscribeOn(@NonNull Executor subscribeExecutor) {
|
||||
mSubscriberThread = subscribeExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the Observable what Executor the onSubscribe should observe
|
||||
* the work on.
|
||||
*
|
||||
* @param observerExecutor the Executor to run to callback on.
|
||||
* @return returns this so that calls can be conveniently chained.
|
||||
*/
|
||||
public Observable<T> observeOn(@NonNull Executor observerExecutor) {
|
||||
mObserverThread = observerExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes immediately to the Observable and ignores
|
||||
* all onComplete and onNext calls.
|
||||
*/
|
||||
public void subscribe() {
|
||||
executeOnSubscriberThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mAction.onSubscribe(new Subscriber<T>() {
|
||||
@Override
|
||||
public void unsubscribe() {}
|
||||
|
||||
@Override
|
||||
public void onComplete() {}
|
||||
|
||||
@Override
|
||||
public void onStart() {}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable throwable) {}
|
||||
|
||||
@Override
|
||||
public void onNext(T item) {}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately subscribes to the Observable and starts
|
||||
* sending events from the Observable to the {@link OnSubscribe}.
|
||||
*
|
||||
* @param onSubscribe the class that wishes to receive onNext and
|
||||
* onComplete callbacks from the Observable.
|
||||
*/
|
||||
public Subscription subscribe(@NonNull OnSubscribe<T> onSubscribe) {
|
||||
|
||||
Preconditions.checkNonNull(onSubscribe);
|
||||
|
||||
final Subscriber<T> subscriber = new SubscriberImpl<>(onSubscribe, this);
|
||||
|
||||
subscriber.onStart();
|
||||
|
||||
executeOnSubscriberThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mAction.onSubscribe(subscriber);
|
||||
}
|
||||
});
|
||||
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
private void executeOnObserverThread(@NonNull Runnable runnable) {
|
||||
if (mObserverThread != null) {
|
||||
mObserverThread.execute(runnable);
|
||||
} else {
|
||||
mDefault.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeOnSubscriberThread(@NonNull Runnable runnable) {
|
||||
if (mSubscriberThread != null) {
|
||||
mSubscriberThread.execute(runnable);
|
||||
} else {
|
||||
mDefault.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SubscriberImpl<T> implements Subscriber<T> {
|
||||
|
||||
@Nullable private volatile OnSubscribe<T> mOnSubscribe;
|
||||
@NonNull private final Observable<T> mObservable;
|
||||
private boolean mOnCompleteExecuted = false;
|
||||
private boolean mOnError = false;
|
||||
|
||||
public SubscriberImpl(@NonNull OnSubscribe<T> onSubscribe, @NonNull Observable<T> observable) {
|
||||
mOnSubscribe = onSubscribe;
|
||||
mObservable = observable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe() {
|
||||
mOnSubscribe = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
OnSubscribe<T> onSubscribe = mOnSubscribe;
|
||||
if (!mOnCompleteExecuted && onSubscribe != null && !mOnError) {
|
||||
mOnCompleteExecuted = true;
|
||||
mObservable.executeOnObserverThread(new OnCompleteRunnable<>(onSubscribe));
|
||||
} else if (!mOnError) {
|
||||
Log.e(TAG, "onComplete called more than once");
|
||||
throw new RuntimeException("onComplete called more than once");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
OnSubscribe<T> onSubscribe = mOnSubscribe;
|
||||
if (onSubscribe != null) {
|
||||
mObservable.executeOnObserverThread(new OnStartRunnable<>(onSubscribe));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull final Throwable throwable) {
|
||||
OnSubscribe<T> onSubscribe = mOnSubscribe;
|
||||
if (onSubscribe != null) {
|
||||
mOnError = true;
|
||||
mObservable.executeOnObserverThread(new OnErrorRunnable<>(onSubscribe, throwable));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(final T item) {
|
||||
OnSubscribe<T> onSubscribe = mOnSubscribe;
|
||||
if (!mOnCompleteExecuted && onSubscribe != null) {
|
||||
mObservable.executeOnObserverThread(new OnNextRunnable<>(onSubscribe, item));
|
||||
} else {
|
||||
Log.e(TAG, "onComplete has been already called, onNext should not be called");
|
||||
throw new RuntimeException("onNext should not be called after onComplete has been called");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnCompleteRunnable<T> implements Runnable {
|
||||
private final OnSubscribe<T> onSubscribe;
|
||||
|
||||
public OnCompleteRunnable(@NonNull OnSubscribe<T> onSubscribe) {this.onSubscribe = onSubscribe;}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
onSubscribe.onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnNextRunnable<T> implements Runnable {
|
||||
private final OnSubscribe<T> onSubscribe;
|
||||
private final T item;
|
||||
|
||||
public OnNextRunnable(@NonNull OnSubscribe<T> onSubscribe, T item) {
|
||||
this.onSubscribe = onSubscribe;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
onSubscribe.onNext(item);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnErrorRunnable<T> implements Runnable {
|
||||
private final OnSubscribe<T> onSubscribe;
|
||||
private final Throwable throwable;
|
||||
|
||||
public OnErrorRunnable(@NonNull OnSubscribe<T> onSubscribe, @NonNull Throwable throwable) {
|
||||
this.onSubscribe = onSubscribe;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
onSubscribe.onError(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OnStartRunnable<T> implements Runnable {
|
||||
private final OnSubscribe<T> onSubscribe;
|
||||
|
||||
public OnStartRunnable(@NonNull OnSubscribe<T> onSubscribe) {this.onSubscribe = onSubscribe;}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
onSubscribe.onStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public abstract class OnSubscribe<T> {
|
||||
|
||||
/**
|
||||
* Called when the observable
|
||||
* runs into an error that will
|
||||
* cause it to abort and not finish.
|
||||
* Receiving this callback means that
|
||||
* the observable is dead and no
|
||||
* {@link #onComplete()} or {@link #onNext(Object)}
|
||||
* callbacks will be called.
|
||||
*
|
||||
* @param throwable an optional throwable that could
|
||||
* be sent.
|
||||
*/
|
||||
public void onError(@NonNull Throwable throwable) {}
|
||||
|
||||
/**
|
||||
* Called before the observer begins
|
||||
* to process and emit items or complete.
|
||||
*/
|
||||
public void onStart() {}
|
||||
|
||||
/**
|
||||
* Called when the Observer emits an
|
||||
* item. It can be called multiple times.
|
||||
* It cannot be called after onComplete
|
||||
* has been called.
|
||||
*
|
||||
* @param item the item that has been emitted,
|
||||
* can be null.
|
||||
*/
|
||||
public void onNext(@Nullable T item) {}
|
||||
|
||||
/**
|
||||
* This method is called when the observer is
|
||||
* finished sending the subscriber events. It
|
||||
* is guaranteed that no other methods will be
|
||||
* called on the OnSubscribe after this method
|
||||
* has been called.
|
||||
*/
|
||||
public void onComplete() {}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class Schedulers {
|
||||
private static final Executor sWorker = Executors.newFixedThreadPool(4);
|
||||
private static final Executor sIOWorker = Executors.newSingleThreadExecutor();
|
||||
private static final Executor sMain = new ThreadExecutor(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* The worker thread executor, will
|
||||
* execute work on any one of multiple
|
||||
* threads.
|
||||
*
|
||||
* @return a non-null executor.
|
||||
*/
|
||||
@NonNull
|
||||
public static Executor worker() {
|
||||
return sWorker;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main thread.
|
||||
*
|
||||
* @return a non-null executor that does work on the main thread.
|
||||
*/
|
||||
@NonNull
|
||||
public static Executor main() {
|
||||
return sMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* The io thread.
|
||||
*
|
||||
* @return a non-null executor that does
|
||||
* work on a single thread off the main thread.
|
||||
*/
|
||||
@NonNull
|
||||
public static Executor io() {
|
||||
return sIOWorker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public interface Subscriber<T> extends Subscription {
|
||||
|
||||
/**
|
||||
* Called immediately upon subscribing
|
||||
* and before the Observable begins
|
||||
* emitting items. This should not be
|
||||
* called by the creator of the Observable
|
||||
* and is rather called internally by the
|
||||
* Observable class itself.
|
||||
*/
|
||||
void onStart();
|
||||
|
||||
/**
|
||||
* Called when the observable
|
||||
* runs into an error that will
|
||||
* cause it to abort and not finish.
|
||||
* Receiving this callback means that
|
||||
* the observable is dead and no
|
||||
* {@link #onComplete()} or {@link #onNext(Object)}
|
||||
* callbacks will be called.
|
||||
*
|
||||
* @param throwable an optional throwable that could
|
||||
* be sent.
|
||||
*/
|
||||
void onError(@NonNull Throwable throwable);
|
||||
|
||||
/**
|
||||
* Called when the Observer emits an
|
||||
* item. It can be called multiple times.
|
||||
* It cannot be called after onComplete
|
||||
* has been called.
|
||||
*
|
||||
* @param item the item that has been emitted,
|
||||
* can be null.
|
||||
*/
|
||||
void onNext(@Nullable T item);
|
||||
|
||||
/**
|
||||
* This method is called when the observer is
|
||||
* finished sending the subscriber events. It
|
||||
* is guaranteed that no other methods will be
|
||||
* called on the OnSubscribe after this method
|
||||
* has been called.
|
||||
*/
|
||||
void onComplete();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
public interface Subscription {
|
||||
|
||||
void unsubscribe();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
class ThreadExecutor implements Executor {
|
||||
|
||||
private final Handler mHandler;
|
||||
|
||||
public ThreadExecutor(@NonNull Looper looper) {
|
||||
mHandler = new Handler(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NonNull Runnable command) {
|
||||
mHandler.post(command);
|
||||
}
|
||||
}
|
||||
@@ -385,16 +385,16 @@ public class ArticleTextExtractor {
|
||||
// System.out.println("date modified element " + elem.toString());
|
||||
}
|
||||
|
||||
if ("".equals(dateStr)) {
|
||||
if (dateStr.isEmpty()) {
|
||||
dateStr = SHelper.innerTrim(doc.select("meta[name=utime]").attr("content"));
|
||||
}
|
||||
if ("".equals(dateStr)) {
|
||||
if (dateStr.isEmpty()) {
|
||||
dateStr = SHelper.innerTrim(doc.select("meta[name=pdate]").attr("content"));
|
||||
}
|
||||
if ("".equals(dateStr)) {
|
||||
if (dateStr.isEmpty()) {
|
||||
dateStr = SHelper.innerTrim(doc.select("meta[property=article:published]").attr("content"));
|
||||
}
|
||||
if ("".equals(dateStr)) {
|
||||
if (dateStr.isEmpty()) {
|
||||
return parseDate(dateStr);
|
||||
}
|
||||
|
||||
@@ -491,10 +491,7 @@ public class ArticleTextExtractor {
|
||||
Element el = elems.get(0);
|
||||
if (el.hasAttr("content")) {
|
||||
dateStr = el.attr("content");
|
||||
Date parsedDate = parseDate(dateStr);
|
||||
if (parsedDate != null) {
|
||||
return parsedDate;
|
||||
}
|
||||
return parseDate(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,14 +683,12 @@ public class ArticleTextExtractor {
|
||||
private static Collection<String> extractKeywords(Document doc) {
|
||||
String content = SHelper.innerTrim(doc.select("head meta[name=keywords]").attr("content"));
|
||||
|
||||
if (content != null) {
|
||||
if (content.startsWith("[") && content.endsWith("]"))
|
||||
content = content.substring(1, content.length() - 1);
|
||||
if (content.startsWith("[") && content.endsWith("]"))
|
||||
content = content.substring(1, content.length() - 1);
|
||||
|
||||
String[] split = content.split("\\s*,\\s*");
|
||||
if (split.length > 1 || (split.length > 0 && split[0] != null && !split[0].isEmpty()))
|
||||
return Arrays.asList(split);
|
||||
}
|
||||
String[] split = content.split("\\s*,\\s*");
|
||||
if (split.length > 1 || (split.length > 0 && split[0] != null && !split[0].isEmpty()))
|
||||
return Arrays.asList(split);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -736,8 +731,7 @@ public class ArticleTextExtractor {
|
||||
}
|
||||
|
||||
private static String extractType(Document doc) {
|
||||
String type = SHelper.innerTrim(doc.select("head meta[property=og:type]").attr("content"));
|
||||
return type;
|
||||
return SHelper.innerTrim(doc.select("head meta[property=og:type]").attr("content"));
|
||||
}
|
||||
|
||||
private static String extractSitename(Document doc) {
|
||||
@@ -969,7 +963,7 @@ public class ArticleTextExtractor {
|
||||
return weight;
|
||||
}
|
||||
|
||||
private Element determineImageSource(Element el, List<ImageResult> images) {
|
||||
private static Element determineImageSource(Element el, List<ImageResult> images) {
|
||||
int maxWeight = 0;
|
||||
Element maxNode = null;
|
||||
Elements els = el.select("img");
|
||||
@@ -1143,13 +1137,14 @@ public class ArticleTextExtractor {
|
||||
}
|
||||
|
||||
private static String cleanTitle(String title) {
|
||||
StringBuilder res = new StringBuilder();
|
||||
|
||||
// int index = title.lastIndexOf("|");
|
||||
// if (index > 0 && title.length() / 2 < index)
|
||||
// title = title.substring(0, index + 1);
|
||||
|
||||
int counter = 0;
|
||||
String[] strs = title.split("\\|");
|
||||
StringBuilder res = new StringBuilder(strs.length);
|
||||
for (String part : strs) {
|
||||
if (IGNORED_TITLE_PARTS.contains(part.toLowerCase().trim()))
|
||||
continue;
|
||||
@@ -1190,7 +1185,7 @@ public class ArticleTextExtractor {
|
||||
charlen = 4;
|
||||
} else if (c <= 0xdfff) {
|
||||
charlen = 0;
|
||||
} else if (c <= 0xffff) {
|
||||
} else {
|
||||
charlen = 3;
|
||||
}
|
||||
if (resultlen + charlen > length) {
|
||||
@@ -1208,7 +1203,7 @@ public class ArticleTextExtractor {
|
||||
*
|
||||
* @author Chris Alexander, chris@chris-alexander.co.uk
|
||||
*/
|
||||
private class ImageComparator implements Comparator<ImageResult> {
|
||||
private static class ImageComparator implements Comparator<ImageResult> {
|
||||
|
||||
@Override
|
||||
public int compare(ImageResult o1, ImageResult o2) {
|
||||
|
||||
@@ -35,9 +35,9 @@ import acr.browser.lightning.constant.Constants;
|
||||
*/
|
||||
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 final static String UTF8 = "UTF-8";
|
||||
private final static String ISO = "ISO-8859-1";
|
||||
private final static int K2 = 2048;
|
||||
private int maxBytes = 1000000 / 2;
|
||||
private String encoding;
|
||||
private String url;
|
||||
@@ -99,7 +99,7 @@ public class Converter {
|
||||
* The max bytes that we want to read from the input stream
|
||||
* @return String
|
||||
*/
|
||||
public String streamToString(InputStream is, int maxBytes, String enc) {
|
||||
private 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 ;)
|
||||
@@ -181,8 +181,8 @@ public class Converter {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static String detectCharset(String key, ByteArrayOutputStream bos, BufferedInputStream in,
|
||||
String enc) throws IOException {
|
||||
private static String detectCharset(String key, ByteArrayOutputStream bos, BufferedInputStream in,
|
||||
String enc) throws IOException {
|
||||
|
||||
// Grab better encoding from stream
|
||||
byte[] arr = new byte[K2];
|
||||
@@ -204,24 +204,24 @@ public class Converter {
|
||||
int lastEncIndex;
|
||||
if (startChar == '\'')
|
||||
// if we have charset='something'
|
||||
lastEncIndex = str.indexOf("'", ++encIndex + clength);
|
||||
lastEncIndex = str.indexOf('\'', ++encIndex + clength);
|
||||
else if (startChar == '\"')
|
||||
// if we have charset="something"
|
||||
lastEncIndex = str.indexOf("\"", ++encIndex + clength);
|
||||
lastEncIndex = str.indexOf('\"', ++encIndex + clength);
|
||||
else {
|
||||
// if we have "text/html; charset=utf-8"
|
||||
int first = str.indexOf("\"", encIndex + clength);
|
||||
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);
|
||||
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);
|
||||
int third = str.indexOf('\'', encIndex + clength);
|
||||
if (third > 0)
|
||||
lastEncIndex = Math.min(lastEncIndex, third);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ import java.net.URL;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
/**
|
||||
* Class to fetch articles. This class is thread safe.
|
||||
*
|
||||
@@ -39,6 +42,8 @@ import java.util.zip.InflaterInputStream;
|
||||
*/
|
||||
public class HtmlFetcher {
|
||||
|
||||
private static final Pattern SPACE = Pattern.compile(" ");
|
||||
|
||||
static {
|
||||
SHelper.enableCookieMgmt();
|
||||
SHelper.enableUserAgentOverwrite();
|
||||
@@ -46,28 +51,36 @@ public class HtmlFetcher {
|
||||
}
|
||||
|
||||
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);
|
||||
BufferedReader reader = null;
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
|
||||
String html = new HtmlFetcher().fetchAsString(url, 2000);
|
||||
String outFile = domainStr + counterStr + ".html";
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(outFile));
|
||||
writer.write(html);
|
||||
writer.close();
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
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";
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
writer = new BufferedWriter(new FileWriter(outFile));
|
||||
writer.write(html);
|
||||
}
|
||||
} finally {
|
||||
Utils.close(reader);
|
||||
Utils.close(writer);
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
|
||||
private String referrer = "http://jetsli.de/crawler";
|
||||
@@ -203,8 +216,9 @@ public class HtmlFetcher {
|
||||
}
|
||||
|
||||
// main workhorse to call externally
|
||||
public JResult fetchAndExtract(String url, int timeout, boolean resolve,
|
||||
int maxContentSize, boolean forceReload) throws Exception {
|
||||
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
|
||||
private 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);
|
||||
@@ -297,7 +311,7 @@ public class HtmlFetcher {
|
||||
}
|
||||
|
||||
// Ugly hack to break free from any cached versions, a few URLs required this.
|
||||
public static String getURLtoBreakCache(String url) {
|
||||
private static String getURLtoBreakCache(String url) {
|
||||
try {
|
||||
URL aURL = new URL(url);
|
||||
if (aURL.getQuery() != null && aURL.getQuery().isEmpty()) {
|
||||
@@ -310,7 +324,7 @@ public class HtmlFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
public String lessText(String text) {
|
||||
private String lessText(String text) {
|
||||
if (text == null)
|
||||
return "";
|
||||
|
||||
@@ -324,14 +338,14 @@ public class HtmlFetcher {
|
||||
return SHelper.useDomainOfFirstArg4Second(url, urlOrPath);
|
||||
}
|
||||
|
||||
public String fetchAsString(String urlAsString, int timeout)
|
||||
throws MalformedURLException, IOException {
|
||||
private String fetchAsString(String urlAsString, int timeout)
|
||||
throws 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 {
|
||||
private String fetchAsString(String urlAsString, int timeout, boolean includeSomeGooseOptions)
|
||||
throws IOException {
|
||||
HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, includeSomeGooseOptions);
|
||||
hConn.setInstanceFollowRedirects(true);
|
||||
String encoding = hConn.getContentEncoding();
|
||||
@@ -348,7 +362,7 @@ public class HtmlFetcher {
|
||||
return createConverter(urlAsString).streamToString(is, enc);
|
||||
}
|
||||
|
||||
public static Converter createConverter(String url) {
|
||||
private static Converter createConverter(String url) {
|
||||
return new Converter(url);
|
||||
}
|
||||
|
||||
@@ -360,8 +374,8 @@ public class HtmlFetcher {
|
||||
* @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) {
|
||||
private String getResolvedUrl(String urlAsString, int timeout,
|
||||
int num_redirects) {
|
||||
String newUrl = null;
|
||||
int responseCode = -1;
|
||||
try {
|
||||
@@ -380,10 +394,10 @@ public class HtmlFetcher {
|
||||
newUrl = hConn.getHeaderField("Location");
|
||||
// Note that the max recursion level is 5.
|
||||
if (responseCode / 100 == 3 && newUrl != null && num_redirects < 5) {
|
||||
newUrl = newUrl.replaceAll(" ", "+");
|
||||
newUrl = SPACE.matcher(newUrl).replaceAll("+");
|
||||
// some services use (none-standard) utf8 in their location header
|
||||
if (urlAsString.startsWith("http://bit.ly")
|
||||
|| urlAsString.startsWith("http://is.gd"))
|
||||
if (urlAsString.contains("://bit.ly")
|
||||
|| urlAsString.contains("://is.gd"))
|
||||
newUrl = encodeUriFromHeader(newUrl);
|
||||
|
||||
// AP: This code is not longer need, instead we always follow
|
||||
@@ -412,8 +426,8 @@ public class HtmlFetcher {
|
||||
* 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();
|
||||
private static String encodeUriFromHeader(String badLocation) {
|
||||
StringBuilder sb = new StringBuilder(badLocation.length());
|
||||
|
||||
for (char ch : badLocation.toCharArray()) {
|
||||
if (ch < (char) 128) {
|
||||
@@ -427,8 +441,8 @@ public class HtmlFetcher {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected HttpURLConnection createUrlConnection(String urlAsStr, int timeout,
|
||||
boolean includeSomeGooseOptions) throws MalformedURLException, IOException {
|
||||
private HttpURLConnection createUrlConnection(String urlAsStr, int timeout,
|
||||
boolean includeSomeGooseOptions) throws IOException {
|
||||
URL url = new URL(urlAsStr);
|
||||
//using proxy may increase latency
|
||||
HttpURLConnection hConn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
|
||||
|
||||
@@ -7,15 +7,15 @@ import org.jsoup.nodes.Element;
|
||||
*
|
||||
* @author Chris Alexander, chris@chris-alexander.co.uk
|
||||
*/
|
||||
public class ImageResult {
|
||||
class ImageResult {
|
||||
|
||||
public final String src;
|
||||
private 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;
|
||||
private final String title;
|
||||
private final int height;
|
||||
private final int width;
|
||||
private final String alt;
|
||||
private final boolean noFollow;
|
||||
public Element element;
|
||||
|
||||
public ImageResult(String src, Integer weight, String title, int height, int width, String alt,
|
||||
|
||||
@@ -47,7 +47,7 @@ public class JResult implements Serializable {
|
||||
private Date date;
|
||||
private Collection<String> keywords;
|
||||
private List<ImageResult> images = null;
|
||||
private List<Map<String, String>> links = new ArrayList<>();
|
||||
private final List<Map<String, String>> links = new ArrayList<>();
|
||||
private String type;
|
||||
private String sitename;
|
||||
private String language;
|
||||
@@ -230,7 +230,7 @@ public class JResult implements Serializable {
|
||||
}
|
||||
|
||||
public void addLink(String url, String text, Integer pos) {
|
||||
Map link = new HashMap();
|
||||
Map<String, String> link = new HashMap<>();
|
||||
link.put("url", url);
|
||||
link.put("text", text);
|
||||
link.put("offset", String.valueOf(pos));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Copyright (C) 2010 Peter Karich <>
|
||||
*
|
||||
* <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
|
||||
@@ -20,7 +20,7 @@ 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
|
||||
*/
|
||||
@@ -60,13 +60,12 @@ public class MapEntry<K, V> implements Map.Entry<K, V>, Serializable {
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
if (!(obj instanceof Map<?, ?>))
|
||||
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)));
|
||||
final MapEntry<?, ?> other = (MapEntry<?, ?>) obj;
|
||||
|
||||
return !(this.key != other.key && (this.key == null || !this.key.equals(other.key))) &&
|
||||
!(this.value != other.value && (this.value == null || !this.value.equals(other.value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,7 @@ 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 Pattern unlikelyPattern = Pattern.compile("display:none|visibility:hidden");
|
||||
private final int minFirstParagraphText;
|
||||
private final int minParagraphText;
|
||||
private final List<String> nodesToReplace;
|
||||
@@ -41,8 +41,8 @@ public class OutputFormatter {
|
||||
this(minFirstParagraphText, minParagraphText, NODES_TO_REPLACE);
|
||||
}
|
||||
|
||||
public OutputFormatter(int minFirstParagraphText, int minParagraphText,
|
||||
List<String> nodesToReplace) {
|
||||
private OutputFormatter(int minFirstParagraphText, int minParagraphText,
|
||||
List<String> nodesToReplace) {
|
||||
this.minFirstParagraphText = minFirstParagraphText;
|
||||
this.minParagraphText = minParagraphText;
|
||||
this.nodesToReplace = nodesToReplace;
|
||||
@@ -91,7 +91,7 @@ public class OutputFormatter {
|
||||
* If there are elements inside our top node that have a negative gravity
|
||||
* score remove them
|
||||
*/
|
||||
protected void removeNodesWithNegativeScores(Element topNode) {
|
||||
private void removeNodesWithNegativeScores(Element topNode) {
|
||||
Elements gravityItems = topNode.select("*[gravityScore]");
|
||||
for (Element item : gravityItems) {
|
||||
int score = getScore(item);
|
||||
@@ -102,7 +102,7 @@ public class OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
protected int append(Element node, StringBuilder sb, String tagName) {
|
||||
private 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?
|
||||
@@ -134,14 +134,14 @@ public class OutputFormatter {
|
||||
return countOfP;
|
||||
}
|
||||
|
||||
protected static void setParagraphIndex(Element node, String tagName) {
|
||||
private 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) {
|
||||
private int getMinParagraph(int paragraphIndex) {
|
||||
if (paragraphIndex < 1) {
|
||||
return minFirstParagraphText;
|
||||
} else {
|
||||
@@ -149,7 +149,7 @@ public class OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
protected static int getParagraphIndex(Element el) {
|
||||
private static int getParagraphIndex(Element el) {
|
||||
try {
|
||||
return Integer.parseInt(el.attr("paragraphIndex"));
|
||||
} catch (NumberFormatException ex) {
|
||||
@@ -157,7 +157,7 @@ public class OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
protected static int getScore(Element el) {
|
||||
private static int getScore(Element el) {
|
||||
try {
|
||||
return Integer.parseInt(el.attr("gravityScore"));
|
||||
} catch (Exception ex) {
|
||||
@@ -165,7 +165,7 @@ public class OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
boolean unlikely(Node e) {
|
||||
private boolean unlikely(Node e) {
|
||||
if (e.attr("class") != null && e.attr("class").toLowerCase().contains("caption"))
|
||||
return true;
|
||||
|
||||
@@ -174,7 +174,7 @@ public class OutputFormatter {
|
||||
return unlikelyPattern.matcher(style).find() || unlikelyPattern.matcher(clazz).find();
|
||||
}
|
||||
|
||||
void appendTextSkipHidden(Element e, StringBuilder accum, int indent) {
|
||||
private void appendTextSkipHidden(Element e, StringBuilder accum, int indent) {
|
||||
for (Node child : e.childNodes()) {
|
||||
if (unlikely(child)) {
|
||||
continue;
|
||||
@@ -195,17 +195,17 @@ public class OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean lastCharIsWhitespace(StringBuilder accum) {
|
||||
private static boolean lastCharIsWhitespace(StringBuilder accum) {
|
||||
return accum.length() != 0 && Character.isWhitespace(accum.charAt(accum.length() - 1));
|
||||
}
|
||||
|
||||
protected String node2Text(Element el) {
|
||||
private String node2Text(Element el) {
|
||||
StringBuilder sb = new StringBuilder(200);
|
||||
appendTextSkipHidden(el, sb, 0);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public OutputFormatter setUnlikelyPattern(String unlikelyPattern) {
|
||||
private OutputFormatter setUnlikelyPattern(String unlikelyPattern) {
|
||||
this.unlikelyPattern = Pattern.compile(unlikelyPattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ import javax.net.ssl.X509TrustManager;
|
||||
/**
|
||||
* @author Peter Karich
|
||||
*/
|
||||
public class SHelper {
|
||||
class SHelper {
|
||||
|
||||
public static final String UTF8 = "UTF-8";
|
||||
private static final String UTF8 = "UTF-8";
|
||||
private static final Pattern SPACE = Pattern.compile(" ");
|
||||
|
||||
public static String replaceSpaces(String url) {
|
||||
@@ -72,9 +72,9 @@ public class SHelper {
|
||||
if (str.isEmpty())
|
||||
return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder(str.length());
|
||||
boolean previousSpace = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
for (int i = 0, length = str.length(); i < length; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == ' ' || (int) c == 9 || c == '\n') {
|
||||
previousSpace = true;
|
||||
@@ -95,7 +95,7 @@ public class SHelper {
|
||||
* invalid encoding character occurs.
|
||||
*/
|
||||
public static String encodingCleanup(String str) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder(str.length());
|
||||
boolean startedWithCorrectString = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
@@ -122,7 +122,7 @@ public class SHelper {
|
||||
return str1.substring(res[0], res[1]);
|
||||
}
|
||||
|
||||
public static int[] longestSubstring(String str1, String str2) {
|
||||
private static int[] longestSubstring(String str1, String str2) {
|
||||
if (str1 == null || str1.isEmpty() || str2 == null || str2.isEmpty())
|
||||
return null;
|
||||
|
||||
@@ -193,7 +193,7 @@ public class SHelper {
|
||||
url = url.substring("m.".length());
|
||||
}
|
||||
|
||||
int slashIndex = url.indexOf("/");
|
||||
int slashIndex = url.indexOf('/');
|
||||
if (slashIndex > 0)
|
||||
url = url.substring(0, slashIndex);
|
||||
|
||||
@@ -251,9 +251,9 @@ public class SHelper {
|
||||
}
|
||||
|
||||
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("\\&");
|
||||
if (url.startsWith("https://www.google.com/url?")) {
|
||||
url = url.substring("https://www.google.com/url?".length());
|
||||
String arr[] = urlDecode(url).split("&");
|
||||
for (String str : arr) {
|
||||
if (str.startsWith("q="))
|
||||
return str.substring("q=".length());
|
||||
@@ -264,8 +264,8 @@ public class SHelper {
|
||||
}
|
||||
|
||||
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());
|
||||
if (url.startsWith("https://www.facebook.com/l.php?u=")) {
|
||||
url = url.substring("https://www.facebook.com/l.php?u=".length());
|
||||
return urlDecode(url);
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ public class SHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static String urlDecode(String str) {
|
||||
private static String urlDecode(String str) {
|
||||
try {
|
||||
return URLDecoder.decode(str, UTF8);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
@@ -300,8 +300,8 @@ public class SHelper {
|
||||
return printNode(root, 0);
|
||||
}
|
||||
|
||||
public static String printNode(Element root, int indentation) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
private static String printNode(Element root, int indentation) {
|
||||
StringBuilder sb = new StringBuilder(indentation);
|
||||
for (int i = 0; i < indentation; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class NetworkReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
}
|
||||
public void onReceive(Context context, Intent intent) {}
|
||||
|
||||
public static boolean isConnected(Context context) {
|
||||
public static boolean isConnected(@NonNull Context context) {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm == null)
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package acr.browser.lightning.search;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
class RetrieveSuggestionsTask extends AsyncTask<Void, Void, List<HistoryItem>> {
|
||||
|
||||
private static final String TAG = RetrieveSuggestionsTask.class.getSimpleName();
|
||||
|
||||
private static final Pattern SPACE_PATTERN = Pattern.compile(" ", Pattern.LITERAL);
|
||||
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 String DEFAULT_LANGUAGE = "en";
|
||||
@Nullable private static XmlPullParser sXpp;
|
||||
@Nullable private static String sLanguage;
|
||||
@NonNull private final WeakReference<SuggestionsResult> mResultCallback;
|
||||
@NonNull private final Application mApplication;
|
||||
@NonNull private final String mSearchSubtitle;
|
||||
@NonNull private String mQuery;
|
||||
|
||||
public RetrieveSuggestionsTask(@NonNull String query,
|
||||
@NonNull SuggestionsResult callback,
|
||||
@NonNull Application application) {
|
||||
mQuery = query;
|
||||
mResultCallback = new WeakReference<>(callback);
|
||||
mApplication = application;
|
||||
mSearchSubtitle = mApplication.getString(R.string.suggestion);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static synchronized String getLanguage() {
|
||||
if (sLanguage == null) {
|
||||
sLanguage = Locale.getDefault().getLanguage();
|
||||
}
|
||||
if (TextUtils.isEmpty(sLanguage)) {
|
||||
sLanguage = DEFAULT_LANGUAGE;
|
||||
}
|
||||
return sLanguage;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static synchronized XmlPullParser getParser() throws XmlPullParserException {
|
||||
if (sXpp == null) {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
sXpp = factory.newPullParser();
|
||||
}
|
||||
return sXpp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<HistoryItem> doInBackground(Void... voids) {
|
||||
List<HistoryItem> filter = new ArrayList<>(5);
|
||||
try {
|
||||
mQuery = SPACE_PATTERN.matcher(mQuery).replaceAll("+");
|
||||
URLEncoder.encode(mQuery, ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
File cache = downloadSuggestionsForQuery(mQuery, getLanguage(), mApplication);
|
||||
if (!cache.exists()) {
|
||||
return filter;
|
||||
}
|
||||
InputStream fileInput = null;
|
||||
try {
|
||||
fileInput = new BufferedInputStream(new FileInputStream(cache));
|
||||
XmlPullParser parser = getParser();
|
||||
parser.setInput(fileInput, ENCODING);
|
||||
int eventType = parser.getEventType();
|
||||
int counter = 0;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && "suggestion".equals(parser.getName())) {
|
||||
String suggestion = parser.getAttributeValue(null, "data");
|
||||
filter.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"',
|
||||
suggestion, R.drawable.ic_search));
|
||||
counter++;
|
||||
if (counter >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return filter;
|
||||
} finally {
|
||||
Utils.close(fileInput);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull List<HistoryItem> result) {
|
||||
SuggestionsResult callback = mResultCallback.get();
|
||||
if (callback != null) {
|
||||
callback.resultReceived(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method downloads the search suggestions for the specific query.
|
||||
* NOTE: This is a blocking operation, do not run on the UI thread.
|
||||
*
|
||||
* @param query the query to get suggestions for
|
||||
* @return the cache file containing the suggestions
|
||||
*/
|
||||
@NonNull
|
||||
private static File downloadSuggestionsForQuery(@NonNull String query, String language, @NonNull Application app) {
|
||||
File cacheFile = new File(app.getCacheDir(), query.hashCode() + CACHE_FILE_TYPE);
|
||||
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) {
|
||||
return cacheFile;
|
||||
}
|
||||
if (!isNetworkConnected(app)) {
|
||||
return cacheFile;
|
||||
}
|
||||
InputStream in = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
// Old API that doesn't support HTTPS
|
||||
// http://google.com/complete/search?q= + query + &output=toolbar&hl= + language
|
||||
URL url = new URL("https://suggestqueries.google.com/complete/search?output=toolbar&hl="
|
||||
+ language + "&q=" + query);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() >= HttpURLConnection.HTTP_MULT_CHOICE ||
|
||||
connection.getResponseCode() < HttpURLConnection.HTTP_OK) {
|
||||
Log.e(TAG, "Search API Responded with code: " + connection.getResponseCode());
|
||||
connection.disconnect();
|
||||
return cacheFile;
|
||||
}
|
||||
in = connection.getInputStream();
|
||||
|
||||
if (in != null) {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
fos = new FileOutputStream(cacheFile);
|
||||
int buffer;
|
||||
while ((buffer = in.read()) != -1) {
|
||||
fos.write(buffer);
|
||||
}
|
||||
fos.flush();
|
||||
}
|
||||
connection.disconnect();
|
||||
cacheFile.setLastModified(System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Problem getting search suggestions", e);
|
||||
} finally {
|
||||
Utils.close(in);
|
||||
Utils.close(fos);
|
||||
}
|
||||
return cacheFile;
|
||||
}
|
||||
|
||||
private static boolean isNetworkConnected(@NonNull Context context) {
|
||||
NetworkInfo networkInfo = getActiveNetworkInfo(context);
|
||||
return networkInfo != null && networkInfo.isConnected();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) {
|
||||
ConnectivityManager connectivity = (ConnectivityManager) context
|
||||
.getApplicationContext()
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivity == null) {
|
||||
return null;
|
||||
}
|
||||
return connectivity.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package acr.browser.lightning.search;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
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 java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
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 javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
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;
|
||||
|
||||
public class SuggestionsAdapter extends BaseAdapter implements Filterable, SuggestionsResult {
|
||||
|
||||
private static final String TAG = SuggestionsAdapter.class.getSimpleName();
|
||||
|
||||
private final List<HistoryItem> mHistory = new ArrayList<>(5);
|
||||
private final List<HistoryItem> mBookmarks = new ArrayList<>(5);
|
||||
private final List<HistoryItem> mSuggestions = new ArrayList<>(5);
|
||||
private final List<HistoryItem> mFilteredList = new ArrayList<>(5);
|
||||
private final List<HistoryItem> mAllBookmarks = new ArrayList<>(5);
|
||||
|
||||
private boolean mUseGoogle = true;
|
||||
private boolean mIsExecuting = false;
|
||||
private final boolean mDarkTheme;
|
||||
private final boolean mIncognito;
|
||||
private static final String CACHE_FILE_TYPE = ".sgg";
|
||||
private static final long INTERVAL_DAY = 86400000;
|
||||
private static final int MAX_SUGGESTIONS = 5;
|
||||
private static final SuggestionsComparator sComparator = new SuggestionsComparator();
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
@Nullable private SearchFilter mFilter;
|
||||
@NonNull private final Drawable mSearchDrawable;
|
||||
@NonNull private final Drawable mHistoryDrawable;
|
||||
@NonNull private final Drawable mBookmarkDrawable;
|
||||
|
||||
@Inject HistoryDatabase mDatabaseHandler;
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
public SuggestionsAdapter(@NonNull Context context, boolean dark, boolean incognito) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
|
||||
mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled();
|
||||
mContext = context;
|
||||
mDarkTheme = dark || incognito;
|
||||
mIncognito = incognito;
|
||||
BrowserApp.getTaskThread().execute(new ClearCacheRunnable(BrowserApp.get(context)));
|
||||
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);
|
||||
}
|
||||
|
||||
public void refreshPreferences() {
|
||||
mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled();
|
||||
if (!mUseGoogle) {
|
||||
synchronized (mSuggestions) {
|
||||
mSuggestions.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshBookmarks() {
|
||||
synchronized (SuggestionsAdapter.this) {
|
||||
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;
|
||||
}
|
||||
|
||||
private static class SuggestionHolder {
|
||||
ImageView mImage;
|
||||
TextView mTitle;
|
||||
TextView mUrl;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, ViewGroup parent) {
|
||||
SuggestionHolder holder;
|
||||
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
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 static class ClearCacheRunnable implements Runnable {
|
||||
|
||||
@NonNull
|
||||
private final Application app;
|
||||
|
||||
public ClearCacheRunnable(@NonNull Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
File dir = new File(app.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 static class NameFilter implements FilenameFilter {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, @NonNull String filename) {
|
||||
return filename.endsWith(CACHE_FILE_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SearchFilter extends Filter {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected FilterResults performFiltering(@Nullable CharSequence constraint) {
|
||||
FilterResults results = new FilterResults();
|
||||
if (constraint == null) {
|
||||
return results;
|
||||
}
|
||||
String query = constraint.toString().toLowerCase(Locale.getDefault());
|
||||
if (mUseGoogle && !mIncognito && !mIsExecuting) {
|
||||
mIsExecuting = true;
|
||||
new RetrieveSuggestionsTask(query, SuggestionsAdapter.this, BrowserApp.get(mContext)).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
synchronized (mBookmarks) {
|
||||
mBookmarks.clear();
|
||||
synchronized (SuggestionsAdapter.this) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<HistoryItem> historyList = mDatabaseHandler.findItemsContaining(constraint.toString());
|
||||
synchronized (mHistory) {
|
||||
mHistory.clear();
|
||||
mHistory.addAll(historyList);
|
||||
}
|
||||
results.count = 1;
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertResultToString(@NonNull 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, sComparator);
|
||||
mFilteredList.addAll(filtered);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resultReceived(@NonNull List<HistoryItem> searchResults) {
|
||||
mIsExecuting = false;
|
||||
synchronized (mSuggestions) {
|
||||
mSuggestions.clear();
|
||||
mSuggestions.addAll(searchResults);
|
||||
}
|
||||
synchronized (mFilteredList) {
|
||||
mFilteredList.clear();
|
||||
List<HistoryItem> filtered = getFilteredList();
|
||||
Collections.sort(filtered, sComparator);
|
||||
mFilteredList.addAll(filtered);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private synchronized List<HistoryItem> getFilteredList() {
|
||||
List<HistoryItem> list = new ArrayList<>(5);
|
||||
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 static class SuggestionsComparator implements Comparator<HistoryItem> {
|
||||
|
||||
@Override
|
||||
public int compare(@NonNull HistoryItem lhs, @NonNull 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,21 @@
|
||||
package acr.browser.lightning.search;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
|
||||
interface SuggestionsResult {
|
||||
|
||||
/**
|
||||
* Called when the search suggestions have
|
||||
* been retrieved from the server.
|
||||
*
|
||||
* @param searchResults the results, a valid
|
||||
* list of results. May
|
||||
* be empty.
|
||||
*/
|
||||
void resultReceived(@NonNull List<HistoryItem> searchResults);
|
||||
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package acr.browser.lightning.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -13,9 +15,14 @@ import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
|
||||
@Singleton
|
||||
public class AdBlock {
|
||||
|
||||
private static final String TAG = "AdBlock";
|
||||
@@ -31,34 +38,32 @@ public class AdBlock {
|
||||
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;
|
||||
}
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
private AdBlock(Context context) {
|
||||
@Inject
|
||||
public AdBlock(@NonNull Context context) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
if (mBlockedDomainsList.isEmpty() && Constants.FULL_VERSION) {
|
||||
loadHostsFile(context);
|
||||
}
|
||||
mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled();
|
||||
mBlockAds = mPreferenceManager.getAdBlockEnabled();
|
||||
}
|
||||
|
||||
public void updatePreference() {
|
||||
mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled();
|
||||
mBlockAds = mPreferenceManager.getAdBlockEnabled();
|
||||
}
|
||||
|
||||
private void loadBlockedDomainsList(final Context context) {
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
private void loadBlockedDomainsList(@NonNull final Context context) {
|
||||
BrowserApp.getTaskThread().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
AssetManager asset = context.getAssets();
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
asset.open(BLOCKED_DOMAINS_LIST_FILE_NAME)));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
@@ -67,19 +72,21 @@ public class AdBlock {
|
||||
} 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
public boolean isAd(@Nullable String url) {
|
||||
if (!mBlockAds || url == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -101,11 +108,13 @@ public class AdBlock {
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@NonNull
|
||||
private static String getDomainName(@NonNull String url) throws URISyntaxException {
|
||||
int index = url.indexOf('/', 8);
|
||||
if (index != -1) {
|
||||
url = url.substring(0, index);
|
||||
@@ -126,16 +135,18 @@ public class AdBlock {
|
||||
* 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() {
|
||||
private void loadHostsFile(@NonNull final Context context) {
|
||||
BrowserApp.getTaskThread().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
AssetManager asset = context.getAssets();
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
asset.open(BLOCKED_DOMAINS_LIST_FILE_NAME)));
|
||||
String line;
|
||||
@@ -169,6 +180,5 @@ public class AdBlock {
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
public class DrawableUtils {
|
||||
|
||||
public static Bitmap getRoundedNumberImage(int number, int width, int height, int color, int thickness) {
|
||||
String text;
|
||||
|
||||
if (number > 99) {
|
||||
text = "\u221E";
|
||||
} else {
|
||||
text = String.valueOf(number);
|
||||
}
|
||||
|
||||
Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(image);
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(color);
|
||||
Typeface boldText = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
|
||||
paint.setTypeface(boldText);
|
||||
paint.setTextSize(Utils.dpToPx(14));
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
|
||||
|
||||
int radius = Utils.dpToPx(2);
|
||||
|
||||
RectF outer = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
canvas.drawRoundRect(outer, radius, radius, paint);
|
||||
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
|
||||
radius--;
|
||||
RectF inner = new RectF(thickness, thickness, canvas.getWidth() - thickness, canvas.getHeight() - thickness);
|
||||
canvas.drawRoundRect(inner, radius, radius, paint);
|
||||
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
|
||||
|
||||
int xPos = (canvas.getWidth() / 2);
|
||||
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
|
||||
|
||||
canvas.drawText(String.valueOf(text), xPos, yPos, paint);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
public static int mixColor(float fraction, int startValue, int endValue) {
|
||||
int startInt = startValue;
|
||||
int startA = (startInt >> 24) & 0xff;
|
||||
int startR = (startInt >> 16) & 0xff;
|
||||
int startG = (startInt >> 8) & 0xff;
|
||||
int startB = startInt & 0xff;
|
||||
|
||||
int endInt = endValue;
|
||||
int endA = (endInt >> 24) & 0xff;
|
||||
int endR = (endInt >> 16) & 0xff;
|
||||
int endG = (endInt >> 8) & 0xff;
|
||||
int endB = endInt & 0xff;
|
||||
|
||||
return (startA + (int)(fraction * (endA - startA))) << 24 |
|
||||
(startR + (int)(fraction * (endR - startR))) << 16 |
|
||||
(startG + (int)(fraction * (endG - startG))) << 8 |
|
||||
(startB + (int)(fraction * (endB - startB)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
|
||||
/**
|
||||
* A utility class containing helpful methods
|
||||
* pertaining to file storage.
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* Writes a bundle to persistent storage in the files directory
|
||||
* using the specified file name. This method is a blocking
|
||||
* operation.
|
||||
*
|
||||
* @param app the application needed to obtain the file directory.
|
||||
* @param bundle the bundle to store in persistent storage.
|
||||
* @param name the name of the file to store the bundle in.
|
||||
*/
|
||||
public static void writeBundleToStorage(final @NonNull Application app, final Bundle bundle, final @NonNull String name) {
|
||||
BrowserApp.getIOThread().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
File outputFile = new File(app.getFilesDir(), name);
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
outputStream = new FileOutputStream(outputFile);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
parcel.writeBundle(bundle);
|
||||
outputStream.write(parcel.marshall());
|
||||
outputStream.flush();
|
||||
parcel.recycle();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Unable to write bundle to storage");
|
||||
} finally {
|
||||
Utils.close(outputStream);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to delete the bundle with the specified name.
|
||||
* This is a blocking call and should be used within a worker
|
||||
* thread unless immediate deletion is necessary.
|
||||
*
|
||||
* @param app the application object needed to get the file.
|
||||
* @param name the name of the file.
|
||||
*/
|
||||
public static void deleteBundleInStorage(final @NonNull Application app, final @NonNull String name) {
|
||||
File outputFile = new File(app.getFilesDir(), name);
|
||||
if (outputFile.exists()) {
|
||||
outputFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a bundle from the file with the specified
|
||||
* name in the peristent storage files directory.
|
||||
* This method is a blocking operation.
|
||||
*
|
||||
* @param app the application needed to obtain the files directory.
|
||||
* @param name the name of the file to read from.
|
||||
* @return a valid Bundle loaded using the system class loader
|
||||
* or null if the method was unable to read the Bundle from storage.
|
||||
*/
|
||||
@Nullable
|
||||
public static Bundle readBundleFromStorage(@NonNull Application app, @NonNull String name) {
|
||||
File inputFile = new File(app.getFilesDir(), name);
|
||||
FileInputStream inputStream = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
inputStream = new FileInputStream(inputFile);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
byte[] data = new byte[(int) inputStream.getChannel().size()];
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputStream.read(data, 0, data.length);
|
||||
parcel.unmarshall(data, 0, data.length);
|
||||
parcel.setDataPosition(0);
|
||||
Bundle out = parcel.readBundle(ClassLoader.getSystemClassLoader());
|
||||
out.putAll(out);
|
||||
parcel.recycle();
|
||||
return out;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Unable to read bundle from storage");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputFile.delete();
|
||||
Utils.close(inputStream);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
@@ -15,7 +18,7 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import acr.browser.lightning.controller.BrowserController;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
|
||||
public class IntentUtils {
|
||||
|
||||
@@ -25,14 +28,14 @@ public class IntentUtils {
|
||||
+ // switch on case insensitive matching
|
||||
'('
|
||||
+ // begin group for schema
|
||||
"(?:http|https|file):\\/\\/" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)"
|
||||
"(?:http|https|file)://" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)"
|
||||
+ ')' + "(.*)");
|
||||
|
||||
public IntentUtils(BrowserController controller) {
|
||||
mActivity = controller.getActivity();
|
||||
public IntentUtils(Activity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
public boolean startActivityForUrl(WebView tab, String url) {
|
||||
public boolean startActivityForUrl(@Nullable WebView tab, @NonNull String url) {
|
||||
Intent intent;
|
||||
try {
|
||||
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
|
||||
@@ -41,6 +44,12 @@ public class IntentUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setComponent(null);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
intent.setSelector(null);
|
||||
}
|
||||
|
||||
if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
|
||||
String packagename = intent.getPackage();
|
||||
if (packagename != null) {
|
||||
@@ -53,10 +62,8 @@ public class IntentUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setComponent(null);
|
||||
if (tab != null) {
|
||||
intent.putExtra(mActivity.getPackageName() + ".Origin", 1);
|
||||
intent.putExtra(Constants.INTENT_ORIGIN, 1);
|
||||
}
|
||||
|
||||
Matcher m = ACCEPTED_URI_SCHEMA.matcher(url);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
public class KeyboardHelper {
|
||||
|
||||
public interface KeyboardListener {
|
||||
/**
|
||||
* Called when the visibility of the keyboard changes.
|
||||
* Parameter tells whether the keyboard has been shown
|
||||
* or hidden.
|
||||
*
|
||||
* @param visible true if the keyboard has been shown,
|
||||
* false otherwise.
|
||||
*/
|
||||
void keyboardVisibilityChanged(boolean visible);
|
||||
}
|
||||
|
||||
@NonNull private final View mView;
|
||||
private int mLastRight = -1;
|
||||
private int mLastBottom = -1;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param view the view to listen on, should be
|
||||
* the {@link android.R.id#content} view.
|
||||
*/
|
||||
public KeyboardHelper(@NonNull View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link KeyboardListener} to receive
|
||||
* callbacks when the keyboard is shown for the specific
|
||||
* view. The view used should be the content view as it
|
||||
* will receive resize events from the system.
|
||||
*
|
||||
* @param listener the listener to register to receive events.
|
||||
*/
|
||||
public void registerKeyboardListener(@NonNull final KeyboardListener listener) {
|
||||
mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
Rect rect = new Rect();
|
||||
if (mLastBottom == -1) {
|
||||
mLastBottom = rect.bottom;
|
||||
}
|
||||
if (mLastRight == -1) {
|
||||
mLastRight = rect.right;
|
||||
}
|
||||
mView.getWindowVisibleDisplayFrame(rect);
|
||||
if (mLastRight == rect.right && rect.bottom < mLastBottom) {
|
||||
listener.keyboardVisibilityChanged(true);
|
||||
} else if (mLastRight == rect.right && rect.bottom > mLastBottom) {
|
||||
listener.keyboardVisibilityChanged(false);
|
||||
}
|
||||
mLastBottom = rect.bottom;
|
||||
mLastRight = rect.right;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class MemoryLeakUtils {
|
||||
|
||||
private static final String TAG = MemoryLeakUtils.class.getSimpleName();
|
||||
|
||||
private static Method sFinishInputLocked = null;
|
||||
|
||||
/**
|
||||
* Clears the mNextServedView and mServedView in
|
||||
* InputMethodManager and keeps them from leaking.
|
||||
*
|
||||
* @param application the application needed to get
|
||||
* the InputMethodManager that is
|
||||
* leaking the views.
|
||||
*/
|
||||
public static void clearNextServedView(@NonNull Application application) {
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
|
||||
// This shouldn't be a problem on N
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) application.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
if (sFinishInputLocked == null) {
|
||||
try {
|
||||
sFinishInputLocked = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(TAG, "Unable to find method in clearNextServedView", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sFinishInputLocked != null) {
|
||||
sFinishInputLocked.setAccessible(true);
|
||||
try {
|
||||
sFinishInputLocked.invoke(imm);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Unable to invoke method in clearNextServedView", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class LifecycleAdapter implements Application.ActivityLifecycleCallbacks {
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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,16 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
public class Preconditions {
|
||||
/**
|
||||
* Ensure that an object is not null
|
||||
* and throw a RuntimeException if it
|
||||
* is null.
|
||||
*
|
||||
* @param object check nullness on this object.
|
||||
*/
|
||||
public static void checkNonNull(Object object) {
|
||||
if (object == null) {
|
||||
throw new RuntimeException("Object must not be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-25
@@ -1,49 +1,46 @@
|
||||
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 com.squareup.otto.Bus;
|
||||
|
||||
import net.i2p.android.ui.I2PAndroidHelper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.BrowserApp;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.bus.BrowserEvents;
|
||||
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
|
||||
*/
|
||||
@Singleton
|
||||
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);
|
||||
}
|
||||
@Inject PreferenceManager mPreferences;
|
||||
@Inject I2PAndroidHelper mI2PHelper;
|
||||
@Inject Bus mBus;
|
||||
|
||||
public static ProxyUtils getInstance(@NonNull Context context) {
|
||||
if (mInstance == null) {
|
||||
mInstance = new ProxyUtils(context);
|
||||
}
|
||||
return mInstance;
|
||||
@Inject
|
||||
public ProxyUtils() {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
public void checkForProxy(@NonNull final Activity activity) {
|
||||
boolean useProxy = mPreferences.getUseProxy();
|
||||
|
||||
final boolean orbotInstalled = OrbotHelper.isOrbotInstalled(activity);
|
||||
@@ -106,7 +103,7 @@ public class ProxyUtils {
|
||||
/*
|
||||
* Initialize WebKit Proxying
|
||||
*/
|
||||
private void initializeProxy(Activity activity) {
|
||||
private void initializeProxy(@NonNull Activity activity) {
|
||||
String host;
|
||||
int port;
|
||||
|
||||
@@ -144,13 +141,13 @@ public class ProxyUtils {
|
||||
|
||||
}
|
||||
|
||||
public boolean isProxyReady(Activity activity) {
|
||||
public boolean isProxyReady() {
|
||||
if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) {
|
||||
if (!mI2PHelper.isI2PAndroidRunning()) {
|
||||
Utils.showSnackbar(activity, R.string.i2p_not_running);
|
||||
mBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_not_running));
|
||||
return false;
|
||||
} else if (!mI2PHelper.areTunnelsActive()) {
|
||||
Utils.showSnackbar(activity, R.string.i2p_tunnels_not_ready);
|
||||
mBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_tunnels_not_ready));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +155,7 @@ public class ProxyUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateProxySettings(Activity activity) {
|
||||
public void updateProxySettings(@NonNull Activity activity) {
|
||||
if (mPreferences.getUseProxy()) {
|
||||
initializeProxy(activity);
|
||||
} else {
|
||||
@@ -191,7 +188,7 @@ public class ProxyUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public int setProxyChoice(int choice, Activity activity) {
|
||||
public static int setProxyChoice(int choice, @NonNull Activity activity) {
|
||||
switch (choice) {
|
||||
case Constants.PROXY_ORBOT:
|
||||
if (!OrbotHelper.isOrbotInstalled(activity)) {
|
||||
@@ -201,7 +198,7 @@ public class ProxyUtils {
|
||||
break;
|
||||
|
||||
case Constants.PROXY_I2P:
|
||||
I2PAndroidHelper ih = new I2PAndroidHelper(activity.getApplicationContext());
|
||||
I2PAndroidHelper ih = new I2PAndroidHelper(BrowserApp.get(activity));
|
||||
if (!ih.isI2PAndroidInstalled()) {
|
||||
choice = Constants.NO_PROXY;
|
||||
ih.promptToInstall(activity);
|
||||
@@ -1,22 +1,22 @@
|
||||
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.Color;
|
||||
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.ColorInt;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.ImageView;
|
||||
|
||||
@@ -45,26 +45,28 @@ public class ThemeUtils {
|
||||
return color;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
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);
|
||||
return ContextCompat.getColor(context, R.color.icon_light_theme);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
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);
|
||||
return ContextCompat.getColor(context, R.color.icon_dark_theme);
|
||||
}
|
||||
|
||||
public static void themeImageView(ImageView icon, Context context, boolean dark) {
|
||||
@ColorInt
|
||||
public static int getIconThemeColor(@NonNull Context context, boolean dark) {
|
||||
return (dark) ? getIconDarkThemeColor(context) : getIconLightThemeColor(context);
|
||||
}
|
||||
|
||||
public static void themeImageView(@NonNull ImageView icon, @NonNull 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) {
|
||||
@NonNull
|
||||
public static Bitmap getThemedBitmap(@NonNull 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);
|
||||
@@ -77,47 +79,27 @@ public class ThemeUtils {
|
||||
return resultBitmap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NonNull
|
||||
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;
|
||||
final Drawable drawable = ContextCompat.getDrawable(context, res);
|
||||
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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
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);
|
||||
}
|
||||
@ColorInt final int color = (dark) ? ContextCompat.getColor(context, R.color.selected_dark) :
|
||||
ContextCompat.getColor(context, R.color.selected_light);
|
||||
return new ColorDrawable(color);
|
||||
}
|
||||
|
||||
public static int getThemedTextHintColor(boolean dark){
|
||||
return 0x80ffffff & (dark ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
|
||||
public static int getTextColor(@NonNull Context context) {
|
||||
return getColor(context, android.R.attr.editTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,30 @@
|
||||
*/
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Patterns;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import acr.browser.lightning.constant.BookmarkPage;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.constant.HistoryPage;
|
||||
import acr.browser.lightning.constant.StartPage;
|
||||
|
||||
/**
|
||||
* Utility methods for Url manipulation
|
||||
*/
|
||||
public class UrlUtils {
|
||||
static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
|
||||
private static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
|
||||
"(?i)" + // switch on case insensitive matching
|
||||
"(" + // begin group for schema
|
||||
"(?:http|https|file):\\/\\/" +
|
||||
'(' + // begin group for schema
|
||||
"(?:http|https|file)://" +
|
||||
"|(?:inline|data|about|javascript):" +
|
||||
"|(?:.*:.*@)" +
|
||||
")" +
|
||||
')' +
|
||||
"(.*)");
|
||||
// Google search
|
||||
public final static String QUERY_PLACE_HOLDER = "%s";
|
||||
@@ -54,7 +60,8 @@ public class UrlUtils {
|
||||
* @return a stripped url like "www.google.com", or the original string if it could
|
||||
* not be stripped
|
||||
*/
|
||||
public static String stripUrl(String url) {
|
||||
@Nullable
|
||||
public static String stripUrl(@Nullable String url) {
|
||||
if (url == null) return null;
|
||||
Matcher m = STRIP_URL_PATTERN.matcher(url);
|
||||
if (m.matches()) {
|
||||
@@ -75,7 +82,8 @@ public class UrlUtils {
|
||||
* URL. If false, invalid URLs will return null
|
||||
* @return Original or modified URL
|
||||
*/
|
||||
public static String smartUrlFilter(String url, boolean canBeSearch, String searchUrl) {
|
||||
@NonNull
|
||||
public static String smartUrlFilter(@NonNull String url, boolean canBeSearch, String searchUrl) {
|
||||
String inUrl = url.trim();
|
||||
boolean hasSpace = inUrl.indexOf(' ') != -1;
|
||||
Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
|
||||
@@ -100,11 +108,12 @@ public class UrlUtils {
|
||||
return URLUtil.composeSearchUrl(inUrl,
|
||||
searchUrl, QUERY_PLACE_HOLDER);
|
||||
}
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
/* package */
|
||||
static String fixUrl(String inUrl) {
|
||||
@NonNull
|
||||
static String fixUrl(@NonNull String inUrl) {
|
||||
// FIXME: Converting the url to lower case
|
||||
// duplicates functionality in smartUrlFilter().
|
||||
// However, changing all current callers of fixUrl to
|
||||
@@ -136,7 +145,8 @@ public class UrlUtils {
|
||||
|
||||
// Returns the filtered URL. Cannot return null, but can return an empty string
|
||||
/* package */
|
||||
static String filteredUrl(String inUrl) {
|
||||
@Nullable
|
||||
static String filteredUrl(@Nullable String inUrl) {
|
||||
if (inUrl == null) {
|
||||
return "";
|
||||
}
|
||||
@@ -146,4 +156,44 @@ public class UrlUtils {
|
||||
}
|
||||
return inUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given url is the bookmarks/history page or a normal website
|
||||
*/
|
||||
public static boolean isSpecialUrl(@Nullable String url) {
|
||||
return url != null && url.startsWith(Constants.FILE) &&
|
||||
(url.endsWith(BookmarkPage.FILENAME) ||
|
||||
url.endsWith(HistoryPage.FILENAME) ||
|
||||
url.endsWith(StartPage.FILENAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the url is a url for the bookmark page.
|
||||
*
|
||||
* @param url the url to check, may be null.
|
||||
* @return true if the url is a bookmark url, false otherwise.
|
||||
*/
|
||||
public static boolean isBookmarkUrl(@Nullable String url) {
|
||||
return url != null && url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the url is a url for the history page.
|
||||
*
|
||||
* @param url the url to check, may be null.
|
||||
* @return true if the url is a history url, false otherwise.
|
||||
*/
|
||||
public static boolean isHistoryUrl(@Nullable String url) {
|
||||
return url != null && url.startsWith(Constants.FILE) && url.endsWith(HistoryPage.FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the url is a url for the start page.
|
||||
*
|
||||
* @param url the url to check, may be null.
|
||||
* @return true if the url is a start page url, false otherwise.
|
||||
*/
|
||||
public static boolean isStartPageUrl(@Nullable String url) {
|
||||
return url != null && url.startsWith(Constants.FILE) && url.endsWith(StartPage.FILENAME);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
@@ -11,6 +12,7 @@ import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
@@ -18,19 +20,23 @@ import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.anthonycr.grant.PermissionsManager;
|
||||
import com.anthonycr.grant.PermissionsResultAction;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -40,19 +46,59 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.MainActivity;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.download.DownloadHandler;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
|
||||
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 boolean doesSupportHeaders() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from the specified URL. Handles permissions
|
||||
* requests, and creates all the necessary dialogs that must be
|
||||
* showed to the user.
|
||||
*
|
||||
* @param activity activity needed to created dialogs.
|
||||
* @param url url to download from.
|
||||
* @param userAgent the user agent of the browser.
|
||||
* @param contentDisposition the content description of the file.
|
||||
*/
|
||||
public static void downloadFile(final Activity activity, final PreferenceManager manager, final String url,
|
||||
final String userAgent, final String contentDisposition) {
|
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
String fileName = URLUtil.guessFileName(url, null, null);
|
||||
DownloadHandler.onDownloadStart(activity, manager, url, userAgent, contentDisposition, null);
|
||||
Log.i(Constants.TAG, "Downloading" + fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(String permission) {
|
||||
// TODO Show Message
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new intent that can launch the email
|
||||
* app with a subject, address, body, and cc. It
|
||||
* is used to handle mail:to links.
|
||||
*
|
||||
* @param address the address to send the email to.
|
||||
* @param subject the subject of the email.
|
||||
* @param body the body of the email.
|
||||
* @param cc extra addresses to CC.
|
||||
* @return a valid intent.
|
||||
*/
|
||||
@NonNull
|
||||
public static Intent newEmailIntent(String address, String subject,
|
||||
String body, String cc) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
@@ -64,12 +110,19 @@ public final class Utils {
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void createInformativeDialog(Context context, @StringRes int title, @StringRes int message) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
/**
|
||||
* Creates a dialog with only a title, message, and okay button.
|
||||
*
|
||||
* @param activity the activity needed to create a dialog.
|
||||
* @param title the title of the dialog.
|
||||
* @param message the message of the dialog.
|
||||
*/
|
||||
public static void createInformativeDialog(@NonNull Activity activity, @StringRes int title, @StringRes int message) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(message)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(context.getResources().getString(R.string.action_ok),
|
||||
.setPositiveButton(activity.getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
@@ -79,30 +132,51 @@ public final class Utils {
|
||||
alert.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a snackbar to the user with a String resource.
|
||||
*
|
||||
* @param activity the activity needed to create a snackbar.
|
||||
* @param resource the string resource to show to the user.
|
||||
*/
|
||||
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) {
|
||||
/**
|
||||
* Displays a snackbar to the user with a string message.
|
||||
*
|
||||
* @param activity the activity needed to create a snackbar.
|
||||
* @param message the string message to show to the user.
|
||||
*/
|
||||
public static void showSnackbar(@NonNull Activity activity, @NonNull 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)
|
||||
* Converts Density Pixels (DP) to Pixels (PX).
|
||||
*
|
||||
* @param dp the number of density pixels to convert
|
||||
* @return the number of pixels
|
||||
* @param dp the number of density pixels to convert.
|
||||
* @return the number of pixels that the conversion generates.
|
||||
*/
|
||||
public static int dpToPx(int dp) {
|
||||
public static int dpToPx(float dp) {
|
||||
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
|
||||
return (int) (dp * metrics.density + 0.5f);
|
||||
}
|
||||
|
||||
public static String getDomainName(String url) {
|
||||
/**
|
||||
* Extracts the domain name from a URL.
|
||||
*
|
||||
* @param url the URL to extract the domain from.
|
||||
* @return the domain name, or the URL if the domain
|
||||
* could not be extracted. The domain name may include
|
||||
* HTTPS if the URL is an SSL supported URL.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getDomainName(@Nullable String url) {
|
||||
if (url == null || url.isEmpty()) return "";
|
||||
|
||||
boolean ssl = url.startsWith(Constants.HTTPS);
|
||||
@@ -130,16 +204,11 @@ public final class Utils {
|
||||
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) {
|
||||
public static String[] getArray(@NonNull String input) {
|
||||
return input.split(Constants.SEPARATOR);
|
||||
}
|
||||
|
||||
public static void trimCache(Context context) {
|
||||
public static void trimCache(@NonNull Context context) {
|
||||
try {
|
||||
File dir = context.getCacheDir();
|
||||
|
||||
@@ -151,7 +220,7 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
private static boolean deleteDir(@Nullable File dir) {
|
||||
if (dir != null && dir.isDirectory()) {
|
||||
String[] children = dir.list();
|
||||
for (String aChildren : children) {
|
||||
@@ -172,7 +241,7 @@ public final class Utils {
|
||||
* @param bitmap is the bitmap to pad.
|
||||
* @return the padded bitmap.
|
||||
*/
|
||||
public static Bitmap padFavicon(Bitmap bitmap) {
|
||||
public static Bitmap padFavicon(@NonNull Bitmap bitmap) {
|
||||
int padding = Utils.dpToPx(4);
|
||||
|
||||
Bitmap paddedBitmap = Bitmap.createBitmap(bitmap.getWidth() + padding, bitmap.getHeight()
|
||||
@@ -233,7 +302,7 @@ public final class Utils {
|
||||
* @param context the context needed to obtain the PackageManager
|
||||
* @return true if flash is installed, false otherwise
|
||||
*/
|
||||
public static boolean isFlashInstalled(Context context) {
|
||||
public static boolean isFlashInstalled(@NonNull Context context) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo ai = pm.getApplicationInfo("com.adobe.flashplayer", 0);
|
||||
@@ -252,7 +321,7 @@ public final class Utils {
|
||||
*
|
||||
* @param closeable the object to close
|
||||
*/
|
||||
public static void close(Closeable closeable) {
|
||||
public static void close(@Nullable Closeable closeable) {
|
||||
if (closeable == null)
|
||||
return;
|
||||
try {
|
||||
@@ -262,11 +331,21 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* Utility method to close cursors. Cursor did not
|
||||
* implement Closeable until API 16, so using this
|
||||
* method for when we want to close a cursor.
|
||||
*
|
||||
* @param cursor the cursor to close
|
||||
*/
|
||||
public static void close(@Nullable Cursor cursor) {
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +356,7 @@ public final class Utils {
|
||||
* @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) {
|
||||
public static void drawTrapezoid(@NonNull Canvas canvas, int color, boolean withShader) {
|
||||
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(color);
|
||||
@@ -309,4 +388,33 @@ public final class Utils {
|
||||
canvas.drawPath(wallpath, paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shortcut on the homescreen using the
|
||||
* {@link HistoryItem} information that opens the
|
||||
* browser. The icon, URL, and title are used in
|
||||
* the creation of the shortcut.
|
||||
*
|
||||
* @param activity the activity needed to create
|
||||
* the intent and show a snackbar message
|
||||
* @param item the HistoryItem to create the shortcut from
|
||||
*/
|
||||
public static void createShortcut(@NonNull Activity activity, @NonNull HistoryItem item) {
|
||||
if (TextUtils.isEmpty(item.getUrl())) {
|
||||
return;
|
||||
}
|
||||
Log.d(Constants.TAG, "Creating shortcut: " + item.getTitle() + ' ' + item.getUrl());
|
||||
Intent shortcutIntent = new Intent(activity, MainActivity.class);
|
||||
shortcutIntent.setData(Uri.parse(item.getUrl()));
|
||||
|
||||
final String title = TextUtils.isEmpty(item.getTitle()) ? activity.getString(R.string.untitled) : item.getTitle();
|
||||
|
||||
Intent addIntent = new Intent();
|
||||
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
||||
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
|
||||
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, item.getBitmap());
|
||||
addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
activity.sendBroadcast(addIntent);
|
||||
Utils.showSnackbar(activity, R.string.message_added_to_homescreen);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package acr.browser.lightning.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.provider.Browser;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
import android.webkit.WebIconDatabase;
|
||||
@@ -23,7 +23,9 @@ public class WebUtils {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
c.removeAllCookies(null);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
CookieSyncManager.createInstance(context);
|
||||
//noinspection deprecation
|
||||
c.removeAllCookie();
|
||||
}
|
||||
}
|
||||
@@ -32,19 +34,21 @@ public class WebUtils {
|
||||
WebStorage.getInstance().deleteAllData();
|
||||
}
|
||||
|
||||
public static void clearHistory(@NonNull Context context) {
|
||||
HistoryDatabase.getInstance(context).deleteHistory();
|
||||
public static void clearHistory(@NonNull Context context, @NonNull HistoryDatabase historyDatabase) {
|
||||
historyDatabase.deleteHistory();
|
||||
WebViewDatabase m = WebViewDatabase.getInstance(context);
|
||||
m.clearFormData();
|
||||
m.clearHttpAuthUsernamePassword();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
//noinspection deprecation
|
||||
m.clearUsernamePassword();
|
||||
//noinspection deprecation
|
||||
WebIconDatabase.getInstance().removeAllIcons();
|
||||
}
|
||||
Utils.trimCache(context);
|
||||
}
|
||||
|
||||
public static void clearCache(WebView view) {
|
||||
public static void clearCache(@Nullable WebView view) {
|
||||
if (view == null) return;
|
||||
view.clearCache(true);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.animation.Animation;
|
||||
@@ -39,12 +40,12 @@ public class AnimatedProgressBar extends LinearLayout {
|
||||
private int mDrawWidth = 0;
|
||||
private int mProgressColor;
|
||||
|
||||
public AnimatedProgressBar(Context context, AttributeSet attrs) {
|
||||
public AnimatedProgressBar(@NonNull Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public AnimatedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public AnimatedProgressBar(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
@@ -55,7 +56,7 @@ public class AnimatedProgressBar extends LinearLayout {
|
||||
* @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) {
|
||||
private void init(@NonNull 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
|
||||
@@ -90,7 +91,7 @@ public class AnimatedProgressBar extends LinearLayout {
|
||||
private final Rect mRect = new Rect();
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
mPaint.setColor(mProgressColor);
|
||||
mPaint.setStrokeWidth(10);
|
||||
mRect.right = mRect.left + mDrawWidth;
|
||||
@@ -209,6 +210,7 @@ public class AnimatedProgressBar extends LinearLayout {
|
||||
super.onRestoreInstanceState(state);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package acr.browser.lightning.view;
|
||||
|
||||
import android.app.Application;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
class IconCacheTask implements Runnable {
|
||||
private final Uri uri;
|
||||
private final Bitmap icon;
|
||||
private final Application app;
|
||||
|
||||
public IconCacheTask(final Uri uri, final Bitmap icon, final Application app) {
|
||||
this.uri = uri;
|
||||
this.icon = icon;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String hash = String.valueOf(uri.getHost().hashCode());
|
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost());
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File image = new File(app.getCacheDir(), hash + ".png");
|
||||
fos = new FileOutputStream(image);
|
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(fos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package acr.browser.lightning.view;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.anthonycr.grant.PermissionsManager;
|
||||
import com.anthonycr.grant.PermissionsResultAction;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.controller.UIController;
|
||||
import acr.browser.lightning.utils.Preconditions;
|
||||
|
||||
class LightningChromeClient extends WebChromeClient {
|
||||
|
||||
private static final String TAG = LightningChromeClient.class.getSimpleName();
|
||||
|
||||
private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
|
||||
|
||||
@NonNull private final Activity mActivity;
|
||||
@NonNull private final LightningView mLightningView;
|
||||
@NonNull private final UIController mUIController;
|
||||
|
||||
LightningChromeClient(@NonNull Activity activity, @NonNull LightningView lightningView) {
|
||||
Preconditions.checkNonNull(activity);
|
||||
Preconditions.checkNonNull(lightningView);
|
||||
mActivity = activity;
|
||||
mUIController = (UIController) activity;
|
||||
mLightningView = lightningView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (mLightningView.isShown()) {
|
||||
mUIController.updateProgress(newProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedIcon(@NonNull WebView view, Bitmap icon) {
|
||||
mLightningView.getTitleInfo().setFavicon(icon);
|
||||
mUIController.tabChanged(mLightningView);
|
||||
cacheFavicon(view.getUrl(), icon, mActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Naive caching of the favicon according to the domain name of the URL
|
||||
*
|
||||
* @param icon the icon to cache
|
||||
*/
|
||||
private static void cacheFavicon(@Nullable final String url, @Nullable final Bitmap icon, @NonNull final Context context) {
|
||||
if (icon == null || url == null) return;
|
||||
final Uri uri = Uri.parse(url);
|
||||
if (uri.getHost() == null) {
|
||||
return;
|
||||
}
|
||||
BrowserApp.getIOThread().execute(new IconCacheTask(uri, icon, BrowserApp.get(context)));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceivedTitle(@Nullable WebView view, @Nullable String title) {
|
||||
if (title != null && !title.isEmpty()) {
|
||||
mLightningView.getTitleInfo().setTitle(title);
|
||||
} else {
|
||||
mLightningView.getTitleInfo().setTitle(mActivity.getString(R.string.untitled));
|
||||
}
|
||||
mUIController.tabChanged(mLightningView);
|
||||
if (view != null && view.getUrl() != null) {
|
||||
mUIController.updateHistory(title, view.getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(@NonNull final String origin,
|
||||
@NonNull final GeolocationPermissions.Callback callback) {
|
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, PERMISSIONS, new PermissionsResultAction() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
final boolean remember = true;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
|
||||
builder.setTitle(mActivity.getString(R.string.location));
|
||||
String org;
|
||||
if (origin.length() > 50) {
|
||||
org = origin.subSequence(0, 50) + "...";
|
||||
} else {
|
||||
org = origin;
|
||||
}
|
||||
builder.setMessage(org + mActivity.getString(R.string.message_location))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(mActivity.getString(R.string.action_allow),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
callback.invoke(origin, true, remember);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(mActivity.getString(R.string.action_dont_allow),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
callback.invoke(origin, false, remember);
|
||||
}
|
||||
});
|
||||
AlertDialog alert = builder.create();
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(String permission) {
|
||||
//TODO show message and/or turn off setting
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,
|
||||
Message resultMsg) {
|
||||
mUIController.onCreateWindow(resultMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseWindow(WebView window) {
|
||||
mUIController.onCloseWindow(mLightningView);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||
mUIController.openFileChooser(uploadMsg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
|
||||
mUIController.openFileChooser(uploadMsg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
|
||||
mUIController.openFileChooser(uploadMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||
WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
mUIController.showFileChooser(filePathCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an image that is displayed as a placeholder on a video until the video has initialized
|
||||
* and can begin loading.
|
||||
*
|
||||
* @return a Bitmap that can be used as a place holder for videos.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Bitmap getDefaultVideoPoster() {
|
||||
final Resources resources = mActivity.getResources();
|
||||
return BitmapFactory.decodeResource(resources, android.R.drawable.spinner_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate a view to send to a LightningView when it needs to display a video and has to
|
||||
* show a loading dialog. Inflates a progress view and returns it.
|
||||
*
|
||||
* @return A view that should be used to display the state
|
||||
* of a video's loading progress.
|
||||
*/
|
||||
@Override
|
||||
public View getVideoLoadingProgressView() {
|
||||
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||
return inflater.inflate(R.layout.video_loading_progress, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
mUIController.onHideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
mUIController.onShowCustomView(view, callback);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onShowCustomView(View view, int requestedOrientation,
|
||||
CustomViewCallback callback) {
|
||||
mUIController.onShowCustomView(view, callback, requestedOrientation);
|
||||
}
|
||||
}
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,106 @@
|
||||
package acr.browser.lightning.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
/**
|
||||
* {@link LightningViewTitle} acts as a container class
|
||||
* for the favicon and page title, the information used
|
||||
* by the tab adapters to show the tabs to the user.
|
||||
*/
|
||||
class LightningViewTitle {
|
||||
|
||||
@Nullable private static Bitmap DEFAULT_DARK_ICON;
|
||||
@Nullable private static Bitmap DEFAULT_LIGHT_ICON;
|
||||
|
||||
@Nullable private Bitmap mFavicon = null;
|
||||
@NonNull private String mTitle;
|
||||
@NonNull private final Context mContext;
|
||||
|
||||
public LightningViewTitle(@NonNull Context context) {
|
||||
mContext = context;
|
||||
mTitle = context.getString(R.string.action_new_tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current favicon to a new Bitmap.
|
||||
* May be null, if null, the default will be used.
|
||||
*
|
||||
* @param favicon the potentially null favicon to set.
|
||||
*/
|
||||
public void setFavicon(@Nullable Bitmap favicon) {
|
||||
if (favicon == null) {
|
||||
mFavicon = null;
|
||||
} else {
|
||||
mFavicon = Utils.padFavicon(favicon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to initialize the DEFAULT_ICON variables
|
||||
*
|
||||
* @param context the context needed to initialize the Bitmap.
|
||||
* @param darkTheme whether the icon should be themed dark or not.
|
||||
* @return a not null icon.
|
||||
*/
|
||||
@NonNull
|
||||
private static Bitmap getDefaultIcon(@NonNull Context context, boolean darkTheme) {
|
||||
if (darkTheme) {
|
||||
if (DEFAULT_DARK_ICON == null) {
|
||||
DEFAULT_DARK_ICON = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, true);
|
||||
}
|
||||
return DEFAULT_DARK_ICON;
|
||||
} else {
|
||||
if (DEFAULT_LIGHT_ICON == null) {
|
||||
DEFAULT_LIGHT_ICON = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, false);
|
||||
}
|
||||
return DEFAULT_LIGHT_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current title to a new title.
|
||||
* Must not be null.
|
||||
*
|
||||
* @param title the non-null title to set.
|
||||
*/
|
||||
public void setTitle(@Nullable String title) {
|
||||
if (title == null) {
|
||||
mTitle = "";
|
||||
} else {
|
||||
mTitle = title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current title, which is not null.
|
||||
* Can be an empty string.
|
||||
*
|
||||
* @return the non-null title.
|
||||
*/
|
||||
@NonNull
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the favicon of the page, which is not null.
|
||||
* Either the favicon, or a default icon.
|
||||
*
|
||||
* @return the favicon or a default if that is null.
|
||||
*/
|
||||
@NonNull
|
||||
public Bitmap getFavicon(boolean darkTheme) {
|
||||
if (mFavicon == null) {
|
||||
return getDefaultIcon(mContext, darkTheme);
|
||||
}
|
||||
return mFavicon;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package acr.browser.lightning.view;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.MailTo;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Build;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.InputType;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.controller.UIController;
|
||||
import acr.browser.lightning.utils.AdBlock;
|
||||
import acr.browser.lightning.utils.IntentUtils;
|
||||
import acr.browser.lightning.utils.Preconditions;
|
||||
import acr.browser.lightning.utils.ProxyUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
|
||||
public class LightningWebClient extends WebViewClient {
|
||||
|
||||
@NonNull private final Activity mActivity;
|
||||
@NonNull private final LightningView mLightningView;
|
||||
@NonNull private final UIController mUIController;
|
||||
@NonNull private final IntentUtils mIntentUtils;
|
||||
|
||||
@Inject ProxyUtils mProxyUtils;
|
||||
@Inject AdBlock mAdBlock;
|
||||
|
||||
LightningWebClient(@NonNull Activity activity, @NonNull LightningView lightningView) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
Preconditions.checkNonNull(activity);
|
||||
Preconditions.checkNonNull(lightningView);
|
||||
mActivity = activity;
|
||||
mUIController = (UIController) activity;
|
||||
mLightningView = lightningView;
|
||||
mAdBlock.updatePreference();
|
||||
mIntentUtils = new IntentUtils(activity);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, @NonNull WebResourceRequest request) {
|
||||
if (mAdBlock.isAd(request.getUrl().toString())) {
|
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes());
|
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY);
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
if (mAdBlock.isAd(url)) {
|
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes());
|
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public void onPageFinished(@NonNull WebView view, String url) {
|
||||
if (view.isShown()) {
|
||||
mUIController.updateUrl(url, true);
|
||||
mUIController.setBackButtonEnabled(view.canGoBack());
|
||||
mUIController.setForwardButtonEnabled(view.canGoForward());
|
||||
view.postInvalidate();
|
||||
}
|
||||
if (view.getTitle() == null || view.getTitle().isEmpty()) {
|
||||
mLightningView.getTitleInfo().setTitle(mActivity.getString(R.string.untitled));
|
||||
} else {
|
||||
mLightningView.getTitleInfo().setTitle(view.getTitle());
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT &&
|
||||
mLightningView.getInvertePage()) {
|
||||
view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null);
|
||||
}
|
||||
mUIController.tabChanged(mLightningView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
mLightningView.getTitleInfo().setFavicon(null);
|
||||
if (mLightningView.isShown()) {
|
||||
mUIController.updateUrl(url, false);
|
||||
mUIController.showActionBar();
|
||||
}
|
||||
mUIController.tabChanged(mLightningView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler,
|
||||
final String host, final String realm) {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
|
||||
final EditText name = new EditText(mActivity);
|
||||
final EditText password = new EditText(mActivity);
|
||||
LinearLayout passLayout = new LinearLayout(mActivity);
|
||||
passLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
passLayout.addView(name);
|
||||
passLayout.addView(password);
|
||||
|
||||
name.setHint(mActivity.getString(R.string.hint_username));
|
||||
name.setSingleLine();
|
||||
password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
password.setSingleLine();
|
||||
password.setTransformationMethod(new PasswordTransformationMethod());
|
||||
password.setHint(mActivity.getString(R.string.hint_password));
|
||||
builder.setTitle(mActivity.getString(R.string.title_sign_in));
|
||||
builder.setView(passLayout);
|
||||
builder.setCancelable(true)
|
||||
.setPositiveButton(mActivity.getString(R.string.title_sign_in),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
String user = name.getText().toString();
|
||||
String pass = password.getText().toString();
|
||||
handler.proceed(user.trim(), pass.trim());
|
||||
Log.d(Constants.TAG, "Request Login");
|
||||
|
||||
}
|
||||
})
|
||||
.setNegativeButton(mActivity.getString(R.string.action_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
handler.cancel();
|
||||
}
|
||||
});
|
||||
AlertDialog alert = builder.create();
|
||||
alert.show();
|
||||
|
||||
}
|
||||
|
||||
private boolean mIsRunning = false;
|
||||
private float mZoomScale = 0.0f;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public void onScaleChanged(@NonNull final WebView view, final float oldScale, final float newScale) {
|
||||
if (view.isShown() && mLightningView.mPreferences.getTextReflowEnabled() &&
|
||||
Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||
if (mIsRunning)
|
||||
return;
|
||||
if (Math.abs(mZoomScale - newScale) > 0.01f) {
|
||||
mIsRunning = view.postDelayed(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mZoomScale = newScale;
|
||||
view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null);
|
||||
mIsRunning = false;
|
||||
}
|
||||
|
||||
}, 100);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static List<Integer> getAllSslErrorMessageCodes(@NonNull SslError error) {
|
||||
List<Integer> errorCodeMessageCodes = new ArrayList<>(1);
|
||||
|
||||
if (error.hasError(SslError.SSL_DATE_INVALID)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_date_invalid);
|
||||
}
|
||||
if (error.hasError(SslError.SSL_EXPIRED)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_expired);
|
||||
}
|
||||
if (error.hasError(SslError.SSL_IDMISMATCH)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_domain_mismatch);
|
||||
}
|
||||
if (error.hasError(SslError.SSL_NOTYETVALID)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_not_yet_valid);
|
||||
}
|
||||
if (error.hasError(SslError.SSL_UNTRUSTED)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_untrusted);
|
||||
}
|
||||
if (error.hasError(SslError.SSL_INVALID)) {
|
||||
errorCodeMessageCodes.add(R.string.message_certificate_invalid);
|
||||
}
|
||||
|
||||
return errorCodeMessageCodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, @NonNull SslError error) {
|
||||
List<Integer> errorCodeMessageCodes = getAllSslErrorMessageCodes(error);
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (Integer messageCode : errorCodeMessageCodes) {
|
||||
stringBuilder.append(" - ").append(mActivity.getString(messageCode)).append('\n');
|
||||
}
|
||||
String alertMessage =
|
||||
mActivity.getString(R.string.message_insecure_connection, stringBuilder.toString());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
|
||||
builder.setTitle(mActivity.getString(R.string.title_warning));
|
||||
builder.setMessage(alertMessage)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(mActivity.getString(R.string.action_yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
handler.proceed();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(mActivity.getString(R.string.action_no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
handler.cancel();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFormResubmission(WebView view, @NonNull final Message dontResend, @NonNull final Message resend) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
|
||||
builder.setTitle(mActivity.getString(R.string.title_form_resubmission));
|
||||
builder.setMessage(mActivity.getString(R.string.message_form_resubmission))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(mActivity.getString(R.string.action_yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
resend.sendToTarget();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(mActivity.getString(R.string.action_no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dontResend.sendToTarget();
|
||||
}
|
||||
});
|
||||
AlertDialog alert = builder.create();
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull String url) {
|
||||
// Check if configured proxy is available
|
||||
if (!mProxyUtils.isProxyReady()) {
|
||||
// User has been notified
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<String, String> headers = mLightningView.getRequestHeaders();
|
||||
|
||||
if (mLightningView.isIncognito() && Utils.doesSupportHeaders()) {
|
||||
view.loadUrl(url, headers);
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("about:") && Utils.doesSupportHeaders()) {
|
||||
view.loadUrl(url, headers);
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("mailto:")) {
|
||||
MailTo mailTo = MailTo.parse(url);
|
||||
Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(),
|
||||
mailTo.getBody(), mailTo.getCc());
|
||||
mActivity.startActivity(i);
|
||||
view.reload();
|
||||
return true;
|
||||
} else if (url.startsWith("intent://")) {
|
||||
Intent intent;
|
||||
try {
|
||||
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
|
||||
} catch (URISyntaxException ignored) {
|
||||
intent = null;
|
||||
}
|
||||
if (intent != null) {
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setComponent(null);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
intent.setSelector(null);
|
||||
}
|
||||
try {
|
||||
mActivity.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(Constants.TAG, "ActivityNotFoundException");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIntentUtils.startActivityForUrl(view, url) && Utils.doesSupportHeaders()) {
|
||||
view.loadUrl(url, headers);
|
||||
}
|
||||
return Utils.doesSupportHeaders() || super.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package acr.browser.lightning.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
public class SearchView extends AutoCompleteTextView {
|
||||
|
||||
public interface PreFocusListener {
|
||||
void onPreFocus();
|
||||
}
|
||||
|
||||
@Nullable private PreFocusListener mListener;
|
||||
private boolean mIsBeingClicked;
|
||||
private long mTimePressed;
|
||||
|
||||
public SearchView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SearchView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setOnPreFocusListener(@Nullable PreFocusListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTimePressed = System.currentTimeMillis();
|
||||
mIsBeingClicked = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mIsBeingClicked = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mIsBeingClicked && !isLongPress()) {
|
||||
if (mListener != null) {
|
||||
mListener.onPreFocus();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private boolean isLongPress() {
|
||||
return (System.currentTimeMillis() - mTimePressed) >= ViewConfiguration.getLongPressTimeout();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário