Comparar commits
242 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 | |||
| ab7273106f | |||
| 61b57cd992 | |||
| 3cb576d358 | |||
| b0c1bcc028 | |||
| 1f025debd7 | |||
| 6749ca39b8 | |||
| 3615018816 | |||
| 030b839aa6 | |||
| 2563e81f7a | |||
| 5c2cf07e20 | |||
| 4be31553ad | |||
| 7661ea35ee | |||
| 51f783cea4 | |||
| 74073178bf | |||
| f0c3b743d4 | |||
| 74a75d4adb | |||
| 5628433718 |
@@ -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.1
|
||||
- android-23
|
||||
- android-22
|
||||
- extra-android-support
|
||||
@@ -17,3 +18,4 @@ before_install:
|
||||
install:
|
||||
- ./gradlew
|
||||
script:
|
||||
- ./gradlew assembleDebug --stacktrace
|
||||
|
||||
-139
@@ -1,139 +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/apt/lightningPlus/debug" isTestSource="false" generated="true" />
|
||||
<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.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/palette-v7/23.0.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.0.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.squareup.leakcanary/leakcanary-android/1.3.1/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="leakcanary-analyzer-1.3.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="jsr250-api-1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="butterknife-7.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="dagger-2.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="leakcanary-watcher-1.3.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="otto-1.3.8" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="haha-1.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="palette-v7-23.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="design-23.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="javax.inject-1" level="project" />
|
||||
<orderEntry type="library" exported="" name="jsoup-1.8.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="leakcanary-android-1.3.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" />
|
||||
<orderEntry type="module" module-name="libnetcipher" exported="" />
|
||||
</component>
|
||||
</module>
|
||||
+57
-19
@@ -1,18 +1,27 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
versionName "4.2.3.1"
|
||||
versionName "4.3.3"
|
||||
generatedDensities = []
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
additionalParameters "--no-version-vectors"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
lightningPlus.setRoot('src/LightningPlus')
|
||||
lightningLite.setRoot('src/LightningLite')
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
@@ -26,44 +35,73 @@ android {
|
||||
proguardFiles 'proguard-project.txt'
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
lightningPlus {
|
||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||
applicationId "acr.browser.lightning"
|
||||
versionCode 85
|
||||
versionCode 88
|
||||
}
|
||||
|
||||
lightningLite {
|
||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||
applicationId "acr.browser.barebones"
|
||||
versionCode 86
|
||||
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.1'
|
||||
compile 'com.android.support:appcompat-v7:23.0.1'
|
||||
compile 'com.android.support:design:23.0.1'
|
||||
compile 'com.android.support:recyclerview-v7:23.0.1'
|
||||
compile 'org.jsoup:jsoup:1.8.3'
|
||||
|
||||
// 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'
|
||||
compile 'com.google.dagger:dagger:2.0.1'
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.1'
|
||||
|
||||
// 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'
|
||||
|
||||
// Only Lightning Plus needs the proxy libraries
|
||||
compile 'net.i2p.android:client:0.7'
|
||||
// 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'))
|
||||
compile project(':libnetcipher')
|
||||
|
||||
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
|
||||
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
|
||||
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
// memory leak analysis
|
||||
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
|
||||
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
|
||||
}
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -1,214 +0,0 @@
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
|
||||
import net.i2p.android.ui.I2PAndroidHelper;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
import info.guardianproject.netcipher.web.WebkitProxy;
|
||||
|
||||
/**
|
||||
* 6/4/2015 Anthony Restaino
|
||||
*/
|
||||
public class ProxyUtils {
|
||||
// Helper
|
||||
private final I2PAndroidHelper mI2PHelper;
|
||||
private static boolean mI2PHelperBound;
|
||||
private static boolean mI2PProxyInitialized;
|
||||
private final PreferenceManager mPreferences;
|
||||
private static ProxyUtils mInstance;
|
||||
|
||||
private ProxyUtils(Context context) {
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
mI2PHelper = new I2PAndroidHelper(context.getApplicationContext());
|
||||
}
|
||||
|
||||
public static ProxyUtils getInstance() {
|
||||
if (mInstance == null) {
|
||||
mInstance = new ProxyUtils(BrowserApp.getAppContext());
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
/*
|
||||
* If Orbot/Tor or I2P is installed, prompt the user if they want to enable
|
||||
* proxying for this session
|
||||
*/
|
||||
public void checkForProxy(final Activity activity) {
|
||||
boolean useProxy = mPreferences.getUseProxy();
|
||||
|
||||
final boolean orbotInstalled = OrbotHelper.isOrbotInstalled(activity);
|
||||
boolean orbotChecked = mPreferences.getCheckedForTor();
|
||||
boolean orbot = orbotInstalled && !orbotChecked;
|
||||
|
||||
boolean i2pInstalled = mI2PHelper.isI2PAndroidInstalled();
|
||||
boolean i2pChecked = mPreferences.getCheckedForI2P();
|
||||
boolean i2p = i2pInstalled && !i2pChecked;
|
||||
|
||||
// TODO Is the idea to show this per-session, or only once?
|
||||
if (!useProxy && (orbot || i2p)) {
|
||||
if (orbot) mPreferences.setCheckedForTor(true);
|
||||
if (i2p) mPreferences.setCheckedForI2P(true);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
|
||||
if (orbotInstalled && i2pInstalled) {
|
||||
String[] proxyChoices = activity.getResources().getStringArray(R.array.proxy_choices_array);
|
||||
builder.setTitle(activity.getResources().getString(R.string.http_proxy))
|
||||
.setSingleChoiceItems(proxyChoices, mPreferences.getProxyChoice(),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mPreferences.setProxyChoice(which);
|
||||
}
|
||||
})
|
||||
.setNeutralButton(activity.getResources().getString(R.string.action_ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (mPreferences.getUseProxy())
|
||||
initializeProxy(activity);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mPreferences.setProxyChoice(orbotInstalled ?
|
||||
Constants.PROXY_ORBOT : Constants.PROXY_I2P);
|
||||
initializeProxy(activity);
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mPreferences.setProxyChoice(Constants.NO_PROXY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
builder.setMessage(orbotInstalled ? R.string.use_tor_prompt : R.string.use_i2p_prompt)
|
||||
.setPositiveButton(R.string.yes, dialogClickListener)
|
||||
.setNegativeButton(R.string.no, dialogClickListener);
|
||||
}
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize WebKit Proxying
|
||||
*/
|
||||
private void initializeProxy(Activity activity) {
|
||||
String host;
|
||||
int port;
|
||||
|
||||
switch (mPreferences.getProxyChoice()) {
|
||||
case Constants.NO_PROXY:
|
||||
// We shouldn't be here
|
||||
return;
|
||||
|
||||
case Constants.PROXY_ORBOT:
|
||||
if (!OrbotHelper.isOrbotRunning(activity))
|
||||
OrbotHelper.requestStartTor(activity);
|
||||
host = "localhost";
|
||||
port = 8118;
|
||||
break;
|
||||
|
||||
case Constants.PROXY_I2P:
|
||||
mI2PProxyInitialized = true;
|
||||
if (mI2PHelperBound && !mI2PHelper.isI2PAndroidRunning()) {
|
||||
mI2PHelper.requestI2PAndroidStart(activity);
|
||||
}
|
||||
host = "localhost";
|
||||
port = 4444;
|
||||
break;
|
||||
|
||||
default:
|
||||
host = mPreferences.getProxyHost();
|
||||
port = mPreferences.getProxyPort();
|
||||
}
|
||||
|
||||
try {
|
||||
WebkitProxy.setProxy(BrowserApp.class.getName(), activity.getApplicationContext(), null, host, port);
|
||||
} catch (Exception e) {
|
||||
Log.d(Constants.TAG, "error enabling web proxying", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isProxyReady(Activity activity) {
|
||||
if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) {
|
||||
if (!mI2PHelper.isI2PAndroidRunning()) {
|
||||
Utils.showSnackbar(activity, R.string.i2p_not_running);
|
||||
return false;
|
||||
} else if (!mI2PHelper.areTunnelsActive()) {
|
||||
Utils.showSnackbar(activity, R.string.i2p_tunnels_not_ready);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateProxySettings(Activity activity) {
|
||||
if (mPreferences.getUseProxy()) {
|
||||
initializeProxy(activity);
|
||||
} else {
|
||||
try {
|
||||
WebkitProxy.resetProxy(BrowserApp.class.getName(), activity.getApplicationContext());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mI2PProxyInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
mI2PHelper.unbind();
|
||||
mI2PHelperBound = false;
|
||||
}
|
||||
|
||||
public void onStart(final Activity activity) {
|
||||
if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) {
|
||||
// Try to bind to I2P Android
|
||||
mI2PHelper.bind(new I2PAndroidHelper.Callback() {
|
||||
@Override
|
||||
public void onI2PAndroidBound() {
|
||||
mI2PHelperBound = true;
|
||||
if (mI2PProxyInitialized && !mI2PHelper.isI2PAndroidRunning())
|
||||
mI2PHelper.requestI2PAndroidStart(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static int setProxyChoice(int choice, Activity activity) {
|
||||
switch (choice) {
|
||||
case Constants.PROXY_ORBOT:
|
||||
if (!OrbotHelper.isOrbotInstalled(activity)) {
|
||||
choice = Constants.NO_PROXY;
|
||||
Utils.showSnackbar(activity, R.string.install_orbot);
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.PROXY_I2P:
|
||||
I2PAndroidHelper ih = new I2PAndroidHelper(activity.getApplicationContext());
|
||||
if (!ih.isI2PAndroidInstalled()) {
|
||||
choice = Constants.NO_PROXY;
|
||||
ih.promptToInstall(activity);
|
||||
}
|
||||
break;
|
||||
case Constants.PROXY_MANUAL:
|
||||
break;
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
<?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">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2014 A.C.R. Development -->
|
||||
<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="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
|
||||
<uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS" />
|
||||
<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=".app.BrowserApp"
|
||||
@@ -32,65 +34,82 @@
|
||||
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
|
||||
@@ -99,9 +118,9 @@
|
||||
android:label="@string/settings"
|
||||
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,13 +129,12 @@
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:process=":incognito"
|
||||
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
|
||||
@@ -125,9 +143,9 @@
|
||||
android:label="@string/reading_mode"
|
||||
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>
|
||||
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -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,8 +65,13 @@ 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,23 +22,42 @@ import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
|
||||
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;
|
||||
@@ -48,8 +68,9 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
|
||||
overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale);
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
mInvert = mPreferences.getInvertColors();
|
||||
final int color;
|
||||
if (mInvert) {
|
||||
@@ -63,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);
|
||||
@@ -70,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));
|
||||
@@ -129,66 +148,87 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
}
|
||||
if (getSupportActionBar() != null)
|
||||
getSupportActionBar().setTitle(Utils.getDomainName(mUrl));
|
||||
new PageLoader(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 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) {
|
||||
@@ -219,6 +259,8 @@ public class ReadingActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mPageLoaderSubscription.unsubscribe();
|
||||
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
|
||||
@@ -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,11 +38,31 @@ 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();
|
||||
}
|
||||
@@ -42,8 +73,7 @@ public abstract class ThemableBrowserActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,27 @@ package acr.browser.lightning.app;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.activity.BrowserActivity;
|
||||
import acr.browser.lightning.constant.BookmarkPage;
|
||||
import acr.browser.lightning.dialog.BookmarksDialogBuilder;
|
||||
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.object.SearchAdapter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by Stefano Pacifici on 01/09/15.
|
||||
*/
|
||||
@Singleton
|
||||
@Component(modules = {AppModule.class})
|
||||
public interface AppComponent {
|
||||
@@ -23,9 +34,40 @@ public interface AppComponent {
|
||||
|
||||
void inject(BookmarkSettingsFragment fragment);
|
||||
|
||||
void inject(SearchAdapter adapter);
|
||||
void inject(SuggestionsAdapter adapter);
|
||||
|
||||
void inject(BookmarksDialogBuilder builder);
|
||||
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);
|
||||
|
||||
void inject(BookmarkPage bookmarkPage);
|
||||
}
|
||||
|
||||
@@ -1,41 +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 acr.browser.lightning.database.BookmarkManager;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Created by Stefano Pacifici on 01/09/15.
|
||||
*/
|
||||
@Module
|
||||
public class AppModule {
|
||||
private final BrowserApp app;
|
||||
private final Bus bus;
|
||||
private final BrowserApp mApp;
|
||||
@NonNull private final Bus mBus;
|
||||
|
||||
public AppModule(BrowserApp app) {
|
||||
this.app = app;
|
||||
this.bus = new Bus();
|
||||
this.mApp = app;
|
||||
this.mBus = new Bus();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Application provideApplication() {
|
||||
return mApp;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Context provideContext() {
|
||||
return app.getApplicationContext();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public BookmarkManager provideBookmarkManager() {
|
||||
return new BookmarkManager(app.getApplicationContext());
|
||||
return mApp.getApplicationContext();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Provides
|
||||
public Bus provideBus() {
|
||||
return bus;
|
||||
return mBus;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Provides
|
||||
@Singleton
|
||||
public I2PAndroidHelper provideI2PAndroidHelper() {
|
||||
return new I2PAndroidHelper(mApp.getApplicationContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +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 Context context;
|
||||
private static AppComponent appComponent;
|
||||
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();
|
||||
context = getApplicationContext();
|
||||
LeakCanary.install(this);
|
||||
buildDepencyGraph();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Context getAppContext() {
|
||||
return context;
|
||||
@NonNull
|
||||
public static BrowserApp get(@NonNull Context context) {
|
||||
return (BrowserApp) context.getApplicationContext();
|
||||
}
|
||||
|
||||
public static AppComponent getAppComponent() {
|
||||
return appComponent;
|
||||
return mAppComponent;
|
||||
}
|
||||
|
||||
private void buildDepencyGraph() {
|
||||
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
|
||||
@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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class AsyncExecutor implements Executor {
|
||||
|
||||
private AsyncExecutor() {}
|
||||
|
||||
@NonNull
|
||||
public static AsyncExecutor getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
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;
|
||||
|
||||
@@ -15,7 +18,6 @@ import java.lang.ref.WeakReference;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
@@ -23,22 +25,27 @@ import acr.browser.lightning.utils.Utils;
|
||||
public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
|
||||
private static final String TAG = ImageDownloadTask.class.getSimpleName();
|
||||
private static final File mCacheDir = BrowserApp.getAppContext().getCacheDir();
|
||||
private final WeakReference<ImageView> bmImage;
|
||||
private final HistoryItem mWeb;
|
||||
@NonNull private final WeakReference<ImageView> mFaviconImage;
|
||||
@NonNull private final WeakReference<Context> mContextReference;
|
||||
@NonNull private final HistoryItem mWeb;
|
||||
private final String mUrl;
|
||||
private final Bitmap mDefaultBitmap;
|
||||
@NonNull private final Bitmap mDefaultBitmap;
|
||||
|
||||
public ImageDownloadTask(@NonNull ImageView bmImage, @NonNull HistoryItem web, @NonNull Bitmap defaultBitmap) {
|
||||
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.bmImage = new WeakReference<>(bmImage);
|
||||
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;
|
||||
@@ -46,12 +53,17 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
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(mCacheDir, hash + ".png");
|
||||
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()) {
|
||||
@@ -130,9 +142,19 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
super.onPostExecute(bitmap);
|
||||
AsyncExecutor.getInstance().notifyThreadFinish();
|
||||
final Bitmap fav = Utils.padFavicon(bitmap);
|
||||
ImageView view = bmImage.get();
|
||||
final ImageView view = mFaviconImage.get();
|
||||
if (view != null && view.getTag().equals(mWeb.getUrl().hashCode())) {
|
||||
view.setImageBitmap(fav);
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -2,37 +2,12 @@ package acr.browser.lightning.bus;
|
||||
|
||||
import acr.browser.lightning.database.HistoryItem;
|
||||
|
||||
/**
|
||||
* Created by Stefano Pacifici on 26/08/15.
|
||||
*/
|
||||
public final class BookmarkEvents {
|
||||
|
||||
private BookmarkEvents() {
|
||||
// No instances
|
||||
}
|
||||
|
||||
/**
|
||||
* A bookmark was clicked
|
||||
*/
|
||||
public final static class Clicked {
|
||||
public final HistoryItem bookmark;
|
||||
|
||||
public Clicked(final HistoryItem bookmark) {
|
||||
this.bookmark = bookmark;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to open the bookmark as new tab
|
||||
*/
|
||||
public final static class AsNewTab {
|
||||
public final HistoryItem bookmark;
|
||||
|
||||
public AsNewTab(final HistoryItem bookmark) {
|
||||
this.bookmark = bookmark;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to delete the selected bookmark
|
||||
*/
|
||||
@@ -45,29 +20,11 @@ public final class BookmarkEvents {
|
||||
}
|
||||
|
||||
/**
|
||||
* The user ask to bookmark the currently displayed page
|
||||
* The user ask to add/del a bookmark to the currently displayed page
|
||||
*/
|
||||
public static class WantToBookmarkCurrentPage {
|
||||
public static class ToggleBookmarkForCurrentPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* The bookmark was added
|
||||
*/
|
||||
public static class Added {
|
||||
public final HistoryItem item;
|
||||
|
||||
public Added(final HistoryItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link acr.browser.lightning.fragment.BookmarksFragment} want to know the url (and title)
|
||||
* of the currently shown web page.
|
||||
*/
|
||||
// public static class WantInfoAboutCurrentPage {
|
||||
// }
|
||||
|
||||
/**
|
||||
* 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}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package acr.browser.lightning.bus;
|
||||
|
||||
/**
|
||||
* Created by Stefano Pacifici on 26/08/15.
|
||||
*/
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
public final class BrowserEvents {
|
||||
|
||||
private BrowserEvents() {
|
||||
@@ -10,23 +10,21 @@ public final class BrowserEvents {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to reply to the {@link acr.browser.lightning.fragment.BookmarksFragment} message
|
||||
* {@link acr.browser.lightning.bus.BookmarkEvents.WantToBookmarkCurrentPage}. The interaction
|
||||
* result is a new bookmark added.
|
||||
* 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 AddBookmark {
|
||||
public static class BookmarkAdded {
|
||||
public final String title, url;
|
||||
|
||||
public AddBookmark(final String title, final String url) {
|
||||
public BookmarkAdded(final String title, final String url) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to reply to {@link acr.browser.lightning.fragment.BookmarksFragment} message
|
||||
* {@link acr.browser.lightning.bus.BookmarkEvents.WantInfoAboutCurrentPage}. This is generally
|
||||
* used to update the {@link acr.browser.lightning.fragment.BookmarksFragment} interface.
|
||||
* 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;
|
||||
@@ -41,4 +39,52 @@ public final class BrowserEvents {
|
||||
*/
|
||||
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 {
|
||||
}
|
||||
}
|
||||
@@ -3,31 +3,44 @@
|
||||
*/
|
||||
package acr.browser.lightning.constant;
|
||||
|
||||
import android.content.Context;
|
||||
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 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.HistoryItem;
|
||||
import acr.browser.lightning.utils.ThemeUtils;
|
||||
import acr.browser.lightning.utils.Utils;
|
||||
import acr.browser.lightning.view.LightningView;
|
||||
|
||||
public final class BookmarkPage {
|
||||
public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private static final String HEADING = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" +
|
||||
/**
|
||||
* The bookmark page standard suffix
|
||||
*/
|
||||
public static final String FILENAME = "bookmarks.html";
|
||||
|
||||
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" +
|
||||
@@ -41,7 +54,7 @@ public final class BookmarkPage {
|
||||
"<p class=ellipses>\n" +
|
||||
"<img src='";
|
||||
|
||||
private static final String PART3 = "http://www.google.com/s2/favicons?domain=";
|
||||
private static final String PART3 = "https://www.google.com/s2/favicons?domain=";
|
||||
|
||||
private static final String PART4 = "' />";
|
||||
|
||||
@@ -49,55 +62,90 @@ public final class BookmarkPage {
|
||||
|
||||
private static final String END = "</div></body></html>";
|
||||
|
||||
@Inject
|
||||
BookmarkManager manager;
|
||||
private File mFilesDir;
|
||||
private File mCacheDir;
|
||||
|
||||
private final File FILES_DIR;
|
||||
private final File CACHE_DIR;
|
||||
private final Application mApp;
|
||||
private final BookmarkManager mManager;
|
||||
@NonNull private final WeakReference<LightningView> mTabReference;
|
||||
private final Bitmap mFolderIcon;
|
||||
@NonNull private final String mTitle;
|
||||
|
||||
@Inject
|
||||
public BookmarkPage(Context context) {
|
||||
BrowserApp.getAppComponent().inject(this);
|
||||
FILES_DIR = context.getFilesDir();
|
||||
CACHE_DIR = context.getCacheDir();
|
||||
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;
|
||||
}
|
||||
|
||||
public void buildBookmarkPage(final String folder, final List<HistoryItem> list) {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
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(FILES_DIR, Constants.BOOKMARKS_FILENAME);
|
||||
bookmarkWebPage = new File(mFilesDir, FILENAME);
|
||||
} else {
|
||||
bookmarkWebPage = new File(FILES_DIR, folder + '-' + Constants.BOOKMARKS_FILENAME);
|
||||
bookmarkWebPage = new File(mFilesDir, folder + '-' + FILENAME);
|
||||
}
|
||||
final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING);
|
||||
final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
|
||||
|
||||
final String folderIconPath = Constants.FILE + CACHE_DIR + "/folder.png";
|
||||
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()) {
|
||||
final File folderPage = new File(FILES_DIR, item.getTitle() + '-' + Constants.BOOKMARKS_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(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) {
|
||||
@@ -107,4 +155,8 @@ public final class BookmarkPage {
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
executeOnExecutor(BrowserApp.getIOThread());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,22 @@ public final class Constants {
|
||||
public static final String YANDEX_SEARCH = "https://yandex.ru/yandsearch?lr=21411&text=";
|
||||
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";
|
||||
|
||||
@@ -43,12 +59,9 @@ public final class Constants {
|
||||
public static final int PROXY_I2P = 2;
|
||||
public static final int PROXY_MANUAL = 3;
|
||||
|
||||
/**
|
||||
* The bookmark page standard suffix
|
||||
*/
|
||||
public static final String BOOKMARKS_FILENAME = "bookmarks.html";
|
||||
|
||||
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.app.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,8 +102,22 @@ public class HistoryPage {
|
||||
return Constants.FILE + historyWebPage;
|
||||
}
|
||||
|
||||
private static List<HistoryItem> getWebHistory(Context context) {
|
||||
HistoryDatabase databaseHandler = HistoryDatabase.getInstance();
|
||||
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.app.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> {
|
||||
|
||||
private static final String FILENAME = "homepage.html";
|
||||
public static final String FILENAME = "homepage.html";
|
||||
|
||||
private 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,27 +48,59 @@ 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=\"";
|
||||
|
||||
private 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 = \"";
|
||||
|
||||
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
|
||||
* when it finishes building.
|
||||
*
|
||||
* @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,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 A.C.R. Development
|
||||
*/
|
||||
package acr.browser.lightning.controller;
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -11,6 +11,9 @@ 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 {
|
||||
@@ -19,127 +22,185 @@ public class BookmarkLocalSync {
|
||||
|
||||
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";
|
||||
|
||||
private final Context mContext;
|
||||
@NonNull private final Context mContext;
|
||||
|
||||
public BookmarkLocalSync(Context context) {
|
||||
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() {
|
||||
List<HistoryItem> list = new ArrayList<>();
|
||||
if (!isStockSupported()) {
|
||||
return list;
|
||||
}
|
||||
Cursor cursor = getStockCursor();
|
||||
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);
|
||||
}
|
||||
list.add(new HistoryItem(url, title));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Utils.close(cursor);
|
||||
return list;
|
||||
return getBookmarksFromContentUri(STOCK_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@WorkerThread
|
||||
public List<HistoryItem> getBookmarksFromChrome() {
|
||||
List<HistoryItem> list = new ArrayList<>();
|
||||
if (!isChromeSupported()) {
|
||||
return list;
|
||||
}
|
||||
Cursor cursor = getStockCursor();
|
||||
try {
|
||||
if (cursor != null) {
|
||||
for (int n = 0; n < cursor.getColumnCount(); n++) {
|
||||
Log.d(TAG, cursor.getColumnName(n));
|
||||
}
|
||||
return getBookmarksFromContentUri(CHROME_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
list.add(new HistoryItem(url, title));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Utils.close(cursor);
|
||||
return list;
|
||||
@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 isStockSupported() {
|
||||
Cursor cursor = getStockCursor();
|
||||
Utils.close(cursor);
|
||||
return cursor != null;
|
||||
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
|
||||
public boolean isChromeSupported() {
|
||||
Cursor cursor = getChromeCursor();
|
||||
Utils.close(cursor);
|
||||
return cursor != null;
|
||||
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() {
|
||||
Cursor cursor;
|
||||
Uri uri = Uri.parse(CHROME_BOOKMARKS_CONTENT);
|
||||
try {
|
||||
cursor = mContext.getContentResolver().query(uri,
|
||||
new String[]{COLUMN_URL, COLUMN_TITLE, COLUMN_BOOKMARK}, null, null, null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
return cursor;
|
||||
return getBrowserCursor(CHROME_BOOKMARKS_CONTENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@WorkerThread
|
||||
private Cursor getStockCursor() {
|
||||
Cursor cursor;
|
||||
Uri uri = Uri.parse(STOCK_BOOKMARKS_CONTENT);
|
||||
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,
|
||||
new String[]{COLUMN_URL, COLUMN_TITLE, COLUMN_BOOKMARK}, null, null, null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
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();
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ 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;
|
||||
@@ -49,29 +50,20 @@ public class BookmarkManager {
|
||||
private static final String ORDER = "order";
|
||||
private static final String FILE_BOOKMARKS = "bookmarks.dat";
|
||||
|
||||
private final String DEFAULT_BOOKMARK_TITLE;
|
||||
@NonNull private final String DEFAULT_BOOKMARK_TITLE;
|
||||
|
||||
private Map<String, HistoryItem> mBookmarksMap;
|
||||
// private final List<HistoryItem> mBookmarkList = new ArrayList<>();
|
||||
private String mCurrentFolder = "";
|
||||
private final ExecutorService mExecutor;
|
||||
private boolean mReady = false;
|
||||
private final File mFilesDir;
|
||||
@NonNull private String mCurrentFolder = "";
|
||||
@NonNull private final ExecutorService mExecutor;
|
||||
private File mFilesDir;
|
||||
|
||||
public BookmarkManager(Context context) {
|
||||
@Inject
|
||||
public BookmarkManager(@NonNull Context context) {
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mFilesDir = context.getFilesDir();
|
||||
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled);
|
||||
mExecutor.execute(new BookmarkInitializer(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the BookmarkManager was initialized, false otherwise
|
||||
*/
|
||||
public boolean isReady() {
|
||||
return mReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for bookmark using the url
|
||||
*
|
||||
@@ -97,6 +89,7 @@ public class BookmarkManager {
|
||||
@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);
|
||||
|
||||
@@ -104,10 +97,12 @@ public class BookmarkManager {
|
||||
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) {
|
||||
@@ -132,7 +127,6 @@ public class BookmarkManager {
|
||||
Utils.close(inputStream);
|
||||
}
|
||||
mBookmarksMap = bookmarks;
|
||||
mReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,11 +146,12 @@ public class BookmarkManager {
|
||||
@Override
|
||||
public void run() {
|
||||
final File tempFile = new File(mFilesDir,
|
||||
String.format("bm_%d.dat", System.currentTimeMillis()));
|
||||
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) {
|
||||
@@ -168,7 +163,7 @@ public class BookmarkManager {
|
||||
bookmarkWriter.newLine();
|
||||
}
|
||||
success = true;
|
||||
} catch (IOException | JSONException e) {
|
||||
} catch (@NonNull IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(bookmarkWriter);
|
||||
@@ -176,6 +171,7 @@ public class BookmarkManager {
|
||||
|
||||
if (success) {
|
||||
// Overwrite the bookmarks file by renaming the temp file
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
tempFile.renameTo(bookmarksFile);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +196,7 @@ public class BookmarkManager {
|
||||
*/
|
||||
public synchronized boolean addBookmark(@NonNull HistoryItem item) {
|
||||
final String url = item.getUrl();
|
||||
if (url == null || mBookmarksMap.containsKey(url)) {
|
||||
if (mBookmarksMap.containsKey(url)) {
|
||||
return false;
|
||||
}
|
||||
mBookmarksMap.put(url, item);
|
||||
@@ -213,13 +209,13 @@ public class BookmarkManager {
|
||||
*
|
||||
* @param list the list of HistoryItems to add to bookmarks
|
||||
*/
|
||||
public synchronized void addBookmarkList(List<HistoryItem> list) {
|
||||
public synchronized void addBookmarkList(@Nullable List<HistoryItem> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (HistoryItem item : list) {
|
||||
final String url = item.getUrl();
|
||||
if (url != null && !mBookmarksMap.containsKey(url)) {
|
||||
if (!mBookmarksMap.containsKey(url)) {
|
||||
mBookmarksMap.put(url, item);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +228,7 @@ 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;
|
||||
}
|
||||
@@ -286,13 +282,23 @@ public class BookmarkManager {
|
||||
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())));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method edits a particular bookmark in the bookmark database
|
||||
*
|
||||
* @param oldItem This is the old item that you wish to edit
|
||||
* @param newItem This is the new item that will overwrite the old item
|
||||
*/
|
||||
public synchronized void editBookmark(HistoryItem oldItem, HistoryItem newItem) {
|
||||
public synchronized void editBookmark(@Nullable HistoryItem oldItem, @Nullable HistoryItem newItem) {
|
||||
if (oldItem == null || newItem == null || oldItem.isFolder()) {
|
||||
return;
|
||||
}
|
||||
@@ -317,7 +323,7 @@ public class BookmarkManager {
|
||||
* 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),
|
||||
@@ -331,6 +337,7 @@ public class BookmarkManager {
|
||||
}
|
||||
BufferedWriter bookmarkWriter = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksExport,
|
||||
false));
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -344,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);
|
||||
@@ -360,6 +367,7 @@ public class BookmarkManager {
|
||||
* @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) {
|
||||
final List<HistoryItem> bookmarks = new ArrayList<>(mBookmarksMap.values());
|
||||
if (sort) {
|
||||
@@ -377,8 +385,9 @@ 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 = "";
|
||||
@@ -394,6 +403,36 @@ public class BookmarkManager {
|
||||
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());
|
||||
}
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells you if you are at the root folder or in a subfolder
|
||||
*
|
||||
@@ -408,24 +447,11 @@ public class BookmarkManager {
|
||||
*
|
||||
* @return the current folder
|
||||
*/
|
||||
@Nullable
|
||||
public String getCurrentFolder() {
|
||||
return mCurrentFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method is used internally for searching the bookmarks
|
||||
*
|
||||
* @return a sorted map of all bookmarks, useful for seeing if a bookmark exists
|
||||
*/
|
||||
private Set<String> getBookmarkUrls(List<HistoryItem> list) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
if (!item.isFolder())
|
||||
set.add(item.getUrl());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of all folders.
|
||||
* Folders cannot be empty as they are generated from
|
||||
@@ -433,11 +459,12 @@ public class BookmarkManager {
|
||||
*
|
||||
* @return a list of all folders
|
||||
*/
|
||||
@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 != null && !folderName.isEmpty() && !folders.containsKey(folderName)) {
|
||||
if (!folderName.isEmpty() && !folders.containsKey(folderName)) {
|
||||
final HistoryItem folder = new HistoryItem();
|
||||
folder.setIsFolder(true);
|
||||
folder.setTitle(folderName);
|
||||
@@ -459,11 +486,12 @@ public class BookmarkManager {
|
||||
*
|
||||
* @return a list of folder title strings
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized List<String> getFolderTitles() {
|
||||
final Set<String> folders = new HashSet<>();
|
||||
for (HistoryItem item : mBookmarksMap.values()) {
|
||||
final String folderName = item.getFolder();
|
||||
if (folderName != null && !folderName.isEmpty()) {
|
||||
if (!folderName.isEmpty()) {
|
||||
folders.add(folderName);
|
||||
}
|
||||
}
|
||||
@@ -476,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;
|
||||
@@ -499,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 {
|
||||
@@ -507,29 +536,13 @@ public class BookmarkManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
|
||||
@@ -8,13 +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 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
|
||||
@@ -33,27 +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;
|
||||
|
||||
private boolean mLock;
|
||||
|
||||
public static HistoryDatabase getInstance() {
|
||||
if (mInstance == null || mInstance.isClosed()) {
|
||||
mInstance = new HistoryDatabase(BrowserApp.getAppContext());
|
||||
}
|
||||
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" + ')';
|
||||
@@ -62,78 +69,69 @@ 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 (!mLock) {
|
||||
if (mDatabase != null) {
|
||||
mDatabase.close();
|
||||
mDatabase = null;
|
||||
}
|
||||
if (mDatabase != null) {
|
||||
mDatabase.close();
|
||||
mDatabase = null;
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
private void openIfNecessary() {
|
||||
if (mDatabase == null) {
|
||||
@NonNull
|
||||
private SQLiteDatabase openIfNecessary() {
|
||||
if (mDatabase == null || !mDatabase.isOpen()) {
|
||||
mDatabase = this.getWritableDatabase();
|
||||
}
|
||||
return mDatabase;
|
||||
}
|
||||
|
||||
public synchronized void deleteHistoryItem(String url) {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
public synchronized void deleteHistoryItem(@NonNull String url) {
|
||||
mDatabase = openIfNecessary();
|
||||
mDatabase.delete(TABLE_HISTORY, KEY_URL + " = ?", new String[]{url});
|
||||
mLock = false;
|
||||
}
|
||||
|
||||
public synchronized void visitHistoryItem(String url, String title) {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
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");
|
||||
if (q.getCount() > 0) {
|
||||
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();
|
||||
mLock = false;
|
||||
}
|
||||
|
||||
private synchronized void addHistoryItem(HistoryItem item) {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
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());
|
||||
values.put(KEY_TIME_VISITED, System.currentTimeMillis());
|
||||
mDatabase.insert(TABLE_HISTORY, null, values);
|
||||
mLock = false;
|
||||
}
|
||||
|
||||
String getHistoryItem(String url) {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
@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;
|
||||
@@ -143,14 +141,16 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
mLock = false;
|
||||
return m;
|
||||
}
|
||||
|
||||
public List<HistoryItem> findItemsContaining(String search) {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
@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";
|
||||
@@ -168,13 +168,12 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
} while (cursor.moveToNext() && n < 5);
|
||||
}
|
||||
cursor.close();
|
||||
mLock = false;
|
||||
return itemList;
|
||||
}
|
||||
|
||||
public List<HistoryItem> getLastHundredItems() {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
@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";
|
||||
@@ -192,13 +191,12 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
} while (cursor.moveToNext() && counter < 100);
|
||||
}
|
||||
cursor.close();
|
||||
mLock = false;
|
||||
return itemList;
|
||||
}
|
||||
|
||||
public List<HistoryItem> getAllHistoryItems() {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
@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";
|
||||
@@ -215,18 +213,15 @@ public class HistoryDatabase extends SQLiteOpenHelper {
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
cursor.close();
|
||||
mLock = false;
|
||||
return itemList;
|
||||
}
|
||||
|
||||
public int getHistoryItemsCount() {
|
||||
mLock = true;
|
||||
openIfNecessary();
|
||||
public synchronized int getHistoryItemsCount() {
|
||||
mDatabase = openIfNecessary();
|
||||
String countQuery = "SELECT * FROM " + TABLE_HISTORY;
|
||||
Cursor cursor = mDatabase.rawQuery(countQuery, null);
|
||||
int n = cursor.getCount();
|
||||
cursor.close();
|
||||
mLock = false;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ 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
|
||||
@@ -26,10 +28,9 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
private int mOrder = 0;
|
||||
private boolean mIsFolder = false;
|
||||
|
||||
// Empty constructor
|
||||
public HistoryItem() {}
|
||||
|
||||
public HistoryItem(HistoryItem item) {
|
||||
public HistoryItem(@NonNull HistoryItem item) {
|
||||
this.mUrl = item.mUrl;
|
||||
this.mTitle = item.mTitle;
|
||||
this.mFolder = item.mFolder;
|
||||
@@ -37,15 +38,17 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
this.mIsFolder = item.mIsFolder;
|
||||
}
|
||||
|
||||
// constructor
|
||||
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(@NonNull String url, @NonNull String title, int imageId) {
|
||||
Preconditions.checkNonNull(url);
|
||||
Preconditions.checkNonNull(title);
|
||||
this.mUrl = url;
|
||||
this.mTitle = title;
|
||||
this.mBitmap = null;
|
||||
@@ -64,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;
|
||||
}
|
||||
|
||||
@@ -76,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;
|
||||
}
|
||||
|
||||
@@ -112,6 +115,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
return mIsFolder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTitle;
|
||||
@@ -127,7 +131,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
public boolean equals(@Nullable Object object) {
|
||||
|
||||
if (this == object) return true;
|
||||
if (object == null) return false;
|
||||
|
||||
+134
-29
@@ -1,8 +1,12 @@
|
||||
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;
|
||||
@@ -19,69 +23,75 @@ 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 BookmarksDialogBuilder {
|
||||
public class LightningDialogBuilder {
|
||||
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
@Inject HistoryDatabase mHistoryDatabase;
|
||||
@Inject Bus mEventBus;
|
||||
|
||||
@Inject
|
||||
BookmarkManager bookmarkManager;
|
||||
|
||||
@Inject
|
||||
Bus eventBus;
|
||||
|
||||
@Inject
|
||||
public BookmarksDialogBuilder() {
|
||||
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
|
||||
*
|
||||
* @param context used to show the dialog
|
||||
* @param url the long pressed url
|
||||
*/
|
||||
public void showLongPressedDialogForUrl(final Context context, final String url) {
|
||||
public void showLongPressedDialogForBookmarkUrl(@NonNull final Context context, @NonNull final String url) {
|
||||
final HistoryItem item;
|
||||
if (url.startsWith(Constants.FILE) && url.endsWith(Constants.BOOKMARKS_FILENAME)) {
|
||||
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() - Constants.BOOKMARKS_FILENAME.length() - 1);
|
||||
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 = bookmarkManager.findBookmarkForUrl(url);
|
||||
item = mBookmarkManager.findBookmarkForUrl(url);
|
||||
}
|
||||
if (item != null) {
|
||||
if (item.isFolder()) {
|
||||
showBookmarkFolderLongPressedDialog(context, item);
|
||||
} else {
|
||||
showLongPressedDialogForUrl(context, item);
|
||||
showLongPressedDialogForBookmarkUrl(context, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showLongPressedDialogForUrl(final Context context, final HistoryItem 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:
|
||||
eventBus.post(new BookmarkEvents.AsNewTab(item));
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInNewTab(item.getUrl()));
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
if (bookmarkManager.deleteBookmark(item)) {
|
||||
eventBus.post(new BookmarkEvents.Deleted(item));
|
||||
if (mBookmarkManager.deleteBookmark(item)) {
|
||||
mEventBus.post(new BookmarkEvents.Deleted(item));
|
||||
}
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
@@ -101,7 +111,7 @@ public class BookmarksDialogBuilder {
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showEditBookmarkDialog(final Context context, final HistoryItem item) {
|
||||
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);
|
||||
@@ -113,7 +123,7 @@ public class BookmarksDialogBuilder {
|
||||
(AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder);
|
||||
getFolder.setHint(R.string.folder);
|
||||
getFolder.setText(item.getFolder());
|
||||
final List<String> folders = bookmarkManager.getFolderTitles();
|
||||
final List<String> folders = mBookmarkManager.getFolderTitles();
|
||||
final ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(context,
|
||||
android.R.layout.simple_dropdown_item_1line, folders);
|
||||
getFolder.setThreshold(1);
|
||||
@@ -129,14 +139,14 @@ public class BookmarksDialogBuilder {
|
||||
editedItem.setUrl(getUrl.getText().toString());
|
||||
editedItem.setUrl(getUrl.getText().toString());
|
||||
editedItem.setFolder(getFolder.getText().toString());
|
||||
bookmarkManager.editBookmark(item, editedItem);
|
||||
eventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
|
||||
mBookmarkManager.editBookmark(item, editedItem);
|
||||
mEventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
|
||||
}
|
||||
});
|
||||
editBookmarkDialog.show();
|
||||
}
|
||||
|
||||
public void showBookmarkFolderLongPressedDialog(final Context context, final HistoryItem item) {
|
||||
public void showBookmarkFolderLongPressedDialog(@NonNull final Context context, @NonNull final HistoryItem item) {
|
||||
// assert item.isFolder();
|
||||
final DialogInterface.OnClickListener dialogClickListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@@ -148,9 +158,9 @@ public class BookmarksDialogBuilder {
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
bookmarkManager.deleteFolder(item.getTitle());
|
||||
mBookmarkManager.deleteFolder(item.getTitle());
|
||||
// setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
eventBus.post(new BookmarkEvents.Deleted(item));
|
||||
mEventBus.post(new BookmarkEvents.Deleted(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +175,7 @@ public class BookmarksDialogBuilder {
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showRenameFolderDialog(final Context context, final HistoryItem item) {
|
||||
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);
|
||||
@@ -191,10 +201,105 @@ public class BookmarksDialogBuilder {
|
||||
editedItem.setUrl(Constants.FOLDER + newTitle);
|
||||
editedItem.setFolder(item.getFolder());
|
||||
editedItem.setIsFolder(true);
|
||||
bookmarkManager.renameFolder(oldTitle, newTitle);
|
||||
eventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
|
||||
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,29 +3,35 @@
|
||||
*/
|
||||
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
|
||||
@@ -44,14 +50,14 @@ public class DownloadHandler {
|
||||
* 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
|
||||
@@ -61,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
|
||||
@@ -81,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;
|
||||
@@ -119,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();
|
||||
@@ -139,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;
|
||||
@@ -162,7 +173,7 @@ public class DownloadHandler {
|
||||
// This only happens for very bad urls, we want to catch the
|
||||
// exception here
|
||||
Log.e(TAG, "Exception while trying to parse url '" + url + '\'', e);
|
||||
Utils.showSnackbar(activity, R.string.problem_download);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_download));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,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);
|
||||
@@ -180,26 +191,20 @@ public class DownloadHandler {
|
||||
// or, should it be set to one of several Environment.DIRECTORY* dirs
|
||||
// depending on mimetype?
|
||||
|
||||
String location = PreferenceManager.getInstance().getDownloadDirectory();
|
||||
String location = preferences.getDownloadDirectory();
|
||||
Uri downloadFolder;
|
||||
if (location != null) {
|
||||
location = addNecessarySlashes(location);
|
||||
downloadFolder = Uri.parse(location);
|
||||
} else {
|
||||
location = addNecessarySlashes(DEFAULT_DOWNLOAD_PATH);
|
||||
downloadFolder = Uri.parse(location);
|
||||
PreferenceManager.getInstance().setDownloadDirectory(location);
|
||||
}
|
||||
location = addNecessarySlashes(location);
|
||||
downloadFolder = Uri.parse(location);
|
||||
|
||||
File dir = new File(downloadFolder.getPath());
|
||||
if (!dir.isDirectory() && !dir.mkdirs()) {
|
||||
// Cannot make the directory
|
||||
Utils.showSnackbar(activity, R.string.problem_location_download);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWriteAccessAvailable(downloadFolder)) {
|
||||
Utils.showSnackbar(activity, R.string.problem_location_download);
|
||||
eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download));
|
||||
return;
|
||||
}
|
||||
request.setDestinationUri(Uri.parse(Constants.FILE + location + filename));
|
||||
@@ -214,32 +219,30 @@ public class DownloadHandler {
|
||||
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() {
|
||||
@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);
|
||||
} 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
|
||||
Utils.showSnackbar(activity, R.string.problem_location_download);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
Utils.showSnackbar(activity, activity.getString(R.string.download_pending) + ' ' + filename);
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -255,7 +258,7 @@ public class DownloadHandler {
|
||||
* @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(String directory) {
|
||||
public static boolean isWriteAccessAvailable(@Nullable String directory) {
|
||||
if (directory == null || directory.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -266,6 +269,7 @@ public class DownloadHandler {
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
if (file.createNewFile()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
return true;
|
||||
@@ -286,34 +290,38 @@ public class DownloadHandler {
|
||||
* @param directory the directory to find the first existent parent
|
||||
* @return the first existent parent
|
||||
*/
|
||||
private static String getFirstRealParentDirectory(String directory) {
|
||||
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) {
|
||||
return getFirstRealParentDirectory(parent.substring(0, previousIndex));
|
||||
@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 "/";
|
||||
return directory;
|
||||
}
|
||||
} else {
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWriteAccessAvailable(Uri fileUri) {
|
||||
private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) {
|
||||
File file = new File(fileUri.getPath());
|
||||
try {
|
||||
if (file.createNewFile()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
return true;
|
||||
@@ -322,7 +330,8 @@ public class DownloadHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static String addNecessarySlashes(String originalPath) {
|
||||
@NonNull
|
||||
public static String addNecessarySlashes(@Nullable String originalPath) {
|
||||
if (originalPath == null || originalPath.length() == 0) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +32,7 @@ import acr.browser.lightning.utils.Utils;
|
||||
*/
|
||||
class FetchUrlMimeType extends Thread {
|
||||
|
||||
private final Activity mActivity;
|
||||
private final Context mContext;
|
||||
|
||||
private final DownloadManager.Request mRequest;
|
||||
|
||||
@@ -37,9 +42,9 @@ 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) {
|
||||
mActivity = activity;
|
||||
mContext = context;
|
||||
mRequest = request;
|
||||
mUri = uri;
|
||||
mCookies = cookies;
|
||||
@@ -50,6 +55,7 @@ class FetchUrlMimeType extends Thread {
|
||||
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;
|
||||
@@ -78,7 +84,7 @@ class FetchUrlMimeType extends Thread {
|
||||
contentDisposition = contentDispositionHeader;
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
} catch (@NonNull IllegalArgumentException | IOException ex) {
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
} finally {
|
||||
@@ -101,9 +107,16 @@ class FetchUrlMimeType extends Thread {
|
||||
}
|
||||
|
||||
// Start the download
|
||||
DownloadManager manager = (DownloadManager) mActivity
|
||||
DownloadManager manager = (DownloadManager) mContext
|
||||
.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.enqueue(mRequest);
|
||||
Utils.showSnackbar(mActivity, mActivity.getString(R.string.download_pending) + ' ' + filename);
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package acr.browser.lightning.download;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -35,17 +36,17 @@ class WebAddress {
|
||||
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$_.+!*'(),;?&=]+)?)@)?" +
|
||||
/* 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) throws IllegalArgumentException {
|
||||
public WebAddress(@Nullable String address) throws IllegalArgumentException {
|
||||
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("address can't be null");
|
||||
@@ -113,6 +114,7 @@ class WebAddress {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
|
||||
@@ -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,6 +161,9 @@ 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -172,12 +175,12 @@ 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]);
|
||||
}
|
||||
});
|
||||
@@ -189,12 +192,12 @@ 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]);
|
||||
}
|
||||
|
||||
@@ -6,15 +6,26 @@ 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;
|
||||
@@ -23,10 +34,14 @@ 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.database.HistoryItem;
|
||||
import acr.browser.lightning.utils.PermissionsManager;
|
||||
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 {
|
||||
@@ -34,13 +49,15 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
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;
|
||||
@Inject
|
||||
BookmarkManager mBookmarkManager;
|
||||
@Nullable private Activity mActivity;
|
||||
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
private File[] mFileList;
|
||||
private String[] mFileNameList;
|
||||
private BookmarkLocalSync mSync;
|
||||
@Nullable private BookmarkLocalSync mSync;
|
||||
|
||||
private static final String[] REQUIRED_PERMISSIONS = new String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
@@ -48,16 +65,38 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
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 = null;
|
||||
if (mSync.isStockSupported()) {
|
||||
list = mSync.getBookmarksFromStockBrowser();
|
||||
} else if (mSync.isChromeSupported()) {
|
||||
list = mSync.getBookmarksFromChrome();
|
||||
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 != null && !list.isEmpty()) {
|
||||
if (!list.isEmpty()) {
|
||||
mBookmarkManager.addBookmarkList(list);
|
||||
count = list.size();
|
||||
}
|
||||
@@ -67,14 +106,25 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
@Override
|
||||
protected void onPostExecute(Integer num) {
|
||||
super.onPostExecute(num);
|
||||
if (mActivity != null) {
|
||||
Activity activity = mActivityReference.get();
|
||||
if (activity != null) {
|
||||
int number = num;
|
||||
final String message = mActivity.getResources().getString(R.string.message_import);
|
||||
Utils.showSnackbar(mActivity, number + " " + message);
|
||||
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);
|
||||
@@ -83,12 +133,13 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
addPreferencesFromResource(R.xml.preference_bookmarks);
|
||||
|
||||
mActivity = getActivity();
|
||||
mSync = new BookmarkLocalSync(mActivity);
|
||||
|
||||
initPrefs();
|
||||
|
||||
PermissionsManager permissionsManager = PermissionsManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
permissionsManager.requestPermissionsIfNecessary(getActivity(), REQUIRED_PERMISSIONS);
|
||||
permissionsManager.requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,49 +151,184 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
|
||||
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);
|
||||
|
||||
mSync = new BookmarkLocalSync(mActivity);
|
||||
exportPref.setOnPreferenceClickListener(this);
|
||||
importPref.setOnPreferenceClickListener(this);
|
||||
deletePref.setOnPreferenceClickListener(this);
|
||||
|
||||
exportpref.setOnPreferenceClickListener(this);
|
||||
importpref.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(mInitializeImportPreference).start();
|
||||
}
|
||||
|
||||
private final Runnable mInitializeImportPreference = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Preference importStock = findPreference(SETTINGS_IMPORT_BROWSER);
|
||||
importStock.setEnabled(mSync.isStockSupported() || mSync.isChromeSupported());
|
||||
importStock.setOnPreferenceClickListener(BookmarkSettingsFragment.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:
|
||||
new ImportBookmarksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
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;
|
||||
@@ -175,7 +361,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
|
||||
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());
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -30,40 +31,50 @@ import com.squareup.otto.Bus;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.activity.BrowserActivity;
|
||||
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.BookmarksDialogBuilder;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by Stefano Pacifici on 25/08/15. Based on Anthony C. Restaino's code.
|
||||
*/
|
||||
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;
|
||||
@Inject BookmarkManager mBookmarkManager;
|
||||
|
||||
// Event bus
|
||||
@Inject
|
||||
Bus mEventBus;
|
||||
@Inject Bus mEventBus;
|
||||
|
||||
// Dialog builder
|
||||
@Inject
|
||||
BookmarksDialogBuilder mBookmarksDialogBuilder;
|
||||
@Inject LightningDialogBuilder mBookmarksDialogBuilder;
|
||||
|
||||
@Inject PreferenceManager mPreferenceManager;
|
||||
|
||||
private TabsManager mTabsManager;
|
||||
|
||||
// Adapter
|
||||
private BookmarkViewAdapter mBookmarkAdapter;
|
||||
@@ -81,21 +92,36 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
// Colors
|
||||
private int mIconColor, mScrollIndex;
|
||||
|
||||
// Init asynchronously the bookmark manager
|
||||
private final Runnable mInitBookmarkManager = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Context context = getContext();
|
||||
mBookmarkAdapter = new BookmarkViewAdapter(context, mBookmarks);
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
mBookmarksListView.setAdapter(mBookmarkAdapter);
|
||||
}
|
||||
};
|
||||
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
|
||||
@@ -107,7 +133,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
mScrollIndex = mBookmarksListView.getFirstVisiblePosition();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true), true);
|
||||
} else {
|
||||
mEventBus.post(new BookmarkEvents.Clicked(item));
|
||||
mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(item.getUrl()));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -116,7 +142,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final HistoryItem item = mBookmarks.get(position);
|
||||
handleLongPress(item, position);
|
||||
handleLongPress(item);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -124,17 +150,20 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
if (mBookmarkAdapter != null) {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
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() {
|
||||
@@ -148,26 +177,20 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
// Must be called here, only here we have a reference to the ListView
|
||||
new Thread(mInitBookmarkManager).run();
|
||||
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 onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
// TODO remove dependency on BrowserActivity
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
final Activity activity = getActivity();
|
||||
final PreferenceManager preferenceManager = PreferenceManager.getInstance();
|
||||
boolean darkTheme = preferenceManager.getUseTheme() != 0 || ((BrowserActivity) activity).isIncognito();
|
||||
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);
|
||||
mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -180,21 +203,30 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
mEventBus.unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void addBookmark(final BrowserEvents.AddBookmark event) {
|
||||
final HistoryItem item = new HistoryItem(event.url, event.title);
|
||||
if (mBookmarkManager.addBookmark(item)) {
|
||||
mBookmarks.add(item);
|
||||
Collections.sort(mBookmarks, new BookmarkManager.SortIgnoreCase());
|
||||
mBookmarkAdapter.notifyDataSetChanged();
|
||||
mEventBus.post(new BookmarkEvents.Added(item));
|
||||
updateBookmarkIndicator(event.url);
|
||||
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 currentPageInfo(final BrowserEvents.CurrentPageUrl event) {
|
||||
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
|
||||
@@ -224,7 +256,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void bookmarkDeleted(final BookmarkEvents.Deleted event) {
|
||||
public void bookmarkDeleted(@NonNull final BookmarkEvents.Deleted event) {
|
||||
mBookmarks.remove(event.item);
|
||||
if (event.item.isFolder()) {
|
||||
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
|
||||
@@ -233,7 +265,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
}
|
||||
}
|
||||
|
||||
private void setBookmarkDataSet(List<HistoryItem> items, boolean animate) {
|
||||
private void setBookmarkDataSet(@NonNull List<HistoryItem> items, boolean animate) {
|
||||
mBookmarks.clear();
|
||||
mBookmarks.addAll(items);
|
||||
mBookmarkAdapter.notifyDataSetChanged();
|
||||
@@ -291,19 +323,35 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
private void handleLongPress(final HistoryItem item, final int position) {
|
||||
private void handleLongPress(@NonNull final HistoryItem item) {
|
||||
if (item.isFolder()) {
|
||||
mBookmarksDialogBuilder.showBookmarkFolderLongPressedDialog(getContext(), item);
|
||||
} else {
|
||||
mBookmarksDialogBuilder.showLongPressedDialogForUrl(getContext(), item);
|
||||
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(getContext(), item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
public void onClick(@NonNull View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.action_add_bookmark:
|
||||
mEventBus.post(new BookmarkEvents.WantToBookmarkCurrentPage());
|
||||
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;
|
||||
@@ -319,7 +367,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
|
||||
final Context context;
|
||||
|
||||
public BookmarkViewAdapter(Context context, List<HistoryItem> data) {
|
||||
public BookmarkViewAdapter(Context context, @NonNull List<HistoryItem> data) {
|
||||
super(context, R.layout.bookmark_list_item, data);
|
||||
this.context = context;
|
||||
}
|
||||
@@ -349,7 +397,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
||||
holder.favicon.setImageBitmap(mFolderBitmap);
|
||||
} else if (web.getBitmap() == null) {
|
||||
holder.favicon.setImageBitmap(mWebpageBitmap);
|
||||
new ImageDownloadTask(holder.favicon, web, mWebpageBitmap)
|
||||
new ImageDownloadTask(holder.favicon, web, mWebpageBitmap, context)
|
||||
.executeOnExecutor(AsyncExecutor.getInstance());
|
||||
} else {
|
||||
holder.favicon.setImageBitmap(web.getBitmap());
|
||||
|
||||
@@ -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:
|
||||
@@ -142,14 +139,14 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe
|
||||
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());
|
||||
}
|
||||
|
||||
});
|
||||
@@ -179,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]);
|
||||
}
|
||||
@@ -195,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();
|
||||
}
|
||||
}
|
||||
@@ -203,7 +200,7 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ package acr.browser.lightning.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
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;
|
||||
@@ -23,12 +24,11 @@ import android.widget.LinearLayout;
|
||||
import acr.browser.lightning.R;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.download.DownloadHandler;
|
||||
import acr.browser.lightning.preference.PreferenceManager;
|
||||
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,23 +90,23 @@ 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(mDownloadLocation);
|
||||
|
||||
@@ -139,27 +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();
|
||||
|
||||
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),
|
||||
@@ -167,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);
|
||||
}
|
||||
@@ -184,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),
|
||||
@@ -192,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);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -209,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
|
||||
@@ -234,7 +229,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
break;
|
||||
}
|
||||
|
||||
mPreferences.setProxyChoice(choice);
|
||||
mPreferenceManager.setProxyChoice(choice);
|
||||
if (choice < mProxyChoices.length)
|
||||
proxy.setSummary(mProxyChoices[choice]);
|
||||
}
|
||||
@@ -252,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)
|
||||
@@ -268,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();
|
||||
@@ -285,13 +280,13 @@ 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);
|
||||
}
|
||||
});
|
||||
@@ -302,7 +297,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
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;
|
||||
@@ -320,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:
|
||||
@@ -345,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),
|
||||
@@ -357,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);
|
||||
}
|
||||
});
|
||||
@@ -367,7 +363,7 @@ 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 = 0;
|
||||
@@ -381,7 +377,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case 0:
|
||||
mPreferences.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
mPreferenceManager.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
downloadloc.setSummary(DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
break;
|
||||
case 1:
|
||||
@@ -397,12 +393,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
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));
|
||||
@@ -434,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));
|
||||
}
|
||||
});
|
||||
@@ -448,12 +444,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
final EditText getDownload = new EditText(mActivity);
|
||||
getDownload.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
getDownload.setText(PreferenceManager.getInstance().getDownloadDirectory());
|
||||
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(mPreferences.getDownloadDirectory());
|
||||
getDownload.setText(mPreferenceManager.getDownloadDirectory());
|
||||
|
||||
layout.addView(getDownload);
|
||||
downLocationPicker.setView(layout);
|
||||
@@ -463,7 +459,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String text = getDownload.getText().toString();
|
||||
text = DownloadHandler.addNecessarySlashes(text);
|
||||
mPreferences.setDownloadDirectory(text);
|
||||
mPreferenceManager.setDownloadDirectory(text);
|
||||
downloadloc.setSummary(text);
|
||||
}
|
||||
});
|
||||
@@ -508,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();
|
||||
@@ -531,45 +527,43 @@ 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:
|
||||
mPreferences.setShowTabsInDrawer((Boolean) newValue);
|
||||
cbDrawerTabs.setChecked((Boolean) newValue);
|
||||
mPreferenceManager.setShowTabsInDrawer(checked);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -593,7 +587,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
if (!DownloadHandler.isWriteAccessAvailable(s.toString())) {
|
||||
this.getDownload.setTextColor(this.errorColor);
|
||||
} else {
|
||||
|
||||
@@ -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,13 +159,12 @@ 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), null).show();
|
||||
@@ -164,13 +178,12 @@ 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), null).show();
|
||||
@@ -184,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() {
|
||||
@@ -199,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,11 +1,17 @@
|
||||
package acr.browser.lightning.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import acr.browser.lightning.app.BrowserApp;
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
import acr.browser.lightning.download.DownloadHandler;
|
||||
|
||||
@Singleton
|
||||
public class PreferenceManager {
|
||||
|
||||
private static class Name {
|
||||
@@ -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";
|
||||
@@ -47,6 +52,8 @@ public class PreferenceManager {
|
||||
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";
|
||||
@@ -54,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 final 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() {
|
||||
@@ -109,13 +111,14 @@ 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);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getDownloadDirectory() {
|
||||
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, DownloadHandler.DEFAULT_DOWNLOAD_PATH);
|
||||
}
|
||||
@@ -136,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);
|
||||
}
|
||||
@@ -156,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);
|
||||
}
|
||||
@@ -168,6 +168,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.POPUPS, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getProxyHost() {
|
||||
return mPrefs.getString(Name.USE_PROXY_HOST, "localhost");
|
||||
}
|
||||
@@ -188,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);
|
||||
}
|
||||
@@ -200,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);
|
||||
}
|
||||
@@ -232,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);
|
||||
}
|
||||
|
||||
@@ -240,6 +244,7 @@ public class PreferenceManager {
|
||||
return mPrefs.getBoolean(Name.USE_WIDE_VIEWPORT, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getTextEncoding() {
|
||||
return mPrefs.getString(Name.TEXT_ENCODING, Constants.DEFAULT_ENCODING);
|
||||
}
|
||||
@@ -248,23 +253,39 @@ public class PreferenceManager {
|
||||
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 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);
|
||||
}
|
||||
|
||||
@@ -312,7 +333,7 @@ public class PreferenceManager {
|
||||
putBoolean(Name.COOKIES, enable);
|
||||
}
|
||||
|
||||
public void setDownloadDirectory(String directory) {
|
||||
public void setDownloadDirectory(@NonNull String directory) {
|
||||
putString(Name.DOWNLOAD_DIRECTORY, directory);
|
||||
}
|
||||
|
||||
@@ -332,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);
|
||||
}
|
||||
|
||||
@@ -352,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);
|
||||
}
|
||||
@@ -376,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);
|
||||
}
|
||||
|
||||
@@ -388,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);
|
||||
}
|
||||
|
||||
@@ -408,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>
|
||||
@@ -423,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);
|
||||
}
|
||||
|
||||
@@ -435,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);
|
||||
}
|
||||
}
|
||||
@@ -491,8 +491,7 @@ public class ArticleTextExtractor {
|
||||
Element el = elems.get(0);
|
||||
if (el.hasAttr("content")) {
|
||||
dateStr = el.attr("content");
|
||||
Date parsedDate = parseDate(dateStr);
|
||||
return parsedDate;
|
||||
return parseDate(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ 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.
|
||||
*
|
||||
@@ -49,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";
|
||||
@@ -386,8 +396,8 @@ public class HtmlFetcher {
|
||||
if (responseCode / 100 == 3 && newUrl != null && num_redirects < 5) {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -251,9 +251,9 @@ 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 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+82
-207
@@ -1,11 +1,12 @@
|
||||
package acr.browser.lightning.object;
|
||||
package acr.browser.lightning.search;
|
||||
|
||||
import android.app.Application;
|
||||
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.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -15,26 +16,14 @@ 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 java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -45,86 +34,60 @@ 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 {
|
||||
public class SuggestionsAdapter extends BaseAdapter implements Filterable, SuggestionsResult {
|
||||
|
||||
private static final String TAG = SuggestionsAdapter.class.getSimpleName();
|
||||
|
||||
private static final Pattern SPACE_PATTERN = Pattern.compile(" ", Pattern.LITERAL);
|
||||
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 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;
|
||||
@Inject
|
||||
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 static final SuggestionsComparator mComparator = new SuggestionsComparator();
|
||||
private final String mSearchSubtitle;
|
||||
private SearchFilter mFilter;
|
||||
private final Drawable mSearchDrawable;
|
||||
private final Drawable mHistoryDrawable;
|
||||
private final Drawable mBookmarkDrawable;
|
||||
private static final SuggestionsComparator sComparator = new SuggestionsComparator();
|
||||
|
||||
public SearchAdapter(Context context, boolean dark, boolean incognito) {
|
||||
@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);
|
||||
mDatabaseHandler = HistoryDatabase.getInstance();
|
||||
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
|
||||
mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled();
|
||||
mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled();
|
||||
mContext = context;
|
||||
mSearchSubtitle = mContext.getString(R.string.suggestion);
|
||||
mDarkTheme = dark || incognito;
|
||||
mIncognito = incognito;
|
||||
Thread delete = new Thread(new ClearCacheRunnable());
|
||||
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);
|
||||
delete.setPriority(Thread.MIN_PRIORITY);
|
||||
delete.start();
|
||||
}
|
||||
|
||||
private static void deleteOldCacheFiles() {
|
||||
File dir = new File(BrowserApp.getAppContext().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, String filename) {
|
||||
return filename.endsWith(CACHE_FILE_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void refreshPreferences() {
|
||||
mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled();
|
||||
mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled();
|
||||
if (!mUseGoogle) {
|
||||
synchronized (mSuggestions) {
|
||||
mSuggestions.clear();
|
||||
}
|
||||
}
|
||||
mDatabaseHandler = HistoryDatabase.getInstance();
|
||||
}
|
||||
|
||||
public void refreshBookmarks() {
|
||||
synchronized (mLock) {
|
||||
synchronized (SuggestionsAdapter.this) {
|
||||
mAllBookmarks.clear();
|
||||
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
|
||||
}
|
||||
@@ -145,8 +108,15 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class SuggestionHolder {
|
||||
ImageView mImage;
|
||||
TextView mTitle;
|
||||
TextView mUrl;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
public View getView(int position, @Nullable View convertView, ViewGroup parent) {
|
||||
SuggestionHolder holder;
|
||||
|
||||
if (convertView == null) {
|
||||
@@ -208,30 +178,56 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
|
||||
private static class ClearCacheRunnable implements Runnable {
|
||||
|
||||
@NonNull
|
||||
private final Application app;
|
||||
|
||||
public ClearCacheRunnable(@NonNull Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
deleteOldCacheFiles();
|
||||
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(CharSequence constraint) {
|
||||
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) {
|
||||
new RetrieveSearchSuggestions().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, query);
|
||||
mIsExecuting = true;
|
||||
new RetrieveSuggestionsTask(query, SuggestionsAdapter.this, BrowserApp.get(mContext)).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
synchronized (mBookmarks) {
|
||||
mBookmarks.clear();
|
||||
synchronized (mLock) {
|
||||
synchronized (SuggestionsAdapter.this) {
|
||||
for (int n = 0; n < mAllBookmarks.size(); n++) {
|
||||
if (counter >= 5) {
|
||||
break;
|
||||
@@ -244,13 +240,10 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
mBookmarks.add(mAllBookmarks.get(n));
|
||||
counter++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mDatabaseHandler == null || mDatabaseHandler.isClosed()) {
|
||||
mDatabaseHandler = HistoryDatabase.getInstance();
|
||||
}
|
||||
|
||||
List<HistoryItem> historyList = mDatabaseHandler.findItemsContaining(constraint.toString());
|
||||
synchronized (mHistory) {
|
||||
mHistory.clear();
|
||||
@@ -261,7 +254,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertResultToString(Object resultValue) {
|
||||
public CharSequence convertResultToString(@NonNull Object resultValue) {
|
||||
return ((HistoryItem) resultValue).getUrl();
|
||||
}
|
||||
|
||||
@@ -270,7 +263,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
synchronized (mFilteredList) {
|
||||
mFilteredList.clear();
|
||||
List<HistoryItem> filtered = getFilteredList();
|
||||
Collections.sort(filtered, mComparator);
|
||||
Collections.sort(filtered, sComparator);
|
||||
mFilteredList.addAll(filtered);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
@@ -278,142 +271,24 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
|
||||
}
|
||||
|
||||
private static class SuggestionHolder {
|
||||
ImageView mImage;
|
||||
TextView mTitle;
|
||||
TextView mUrl;
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
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 = SPACE_PATTERN.matcher(query).replaceAll("+");
|
||||
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) {
|
||||
mIsExecuting = false;
|
||||
synchronized (mSuggestions) {
|
||||
mSuggestions.clear();
|
||||
mSuggestions.addAll(result);
|
||||
}
|
||||
synchronized (mFilteredList) {
|
||||
mFilteredList.clear();
|
||||
List<HistoryItem> filtered = getFilteredList();
|
||||
Collections.sort(filtered, mComparator);
|
||||
mFilteredList.addAll(filtered);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private static File downloadSuggestionsForQuery(String query) {
|
||||
File cacheFile = new File(BrowserApp.getAppContext().getCacheDir(), query.hashCode() + CACHE_FILE_TYPE);
|
||||
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) {
|
||||
return cacheFile;
|
||||
}
|
||||
if (!isNetworkConnected(BrowserApp.getAppContext())) {
|
||||
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();
|
||||
}
|
||||
|
||||
private List<HistoryItem> getFilteredList() {
|
||||
@NonNull
|
||||
private synchronized List<HistoryItem> getFilteredList() {
|
||||
List<HistoryItem> list = new ArrayList<>(5);
|
||||
synchronized (mBookmarks) {
|
||||
synchronized (mHistory) {
|
||||
@@ -444,7 +319,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
|
||||
private static class SuggestionsComparator implements Comparator<HistoryItem> {
|
||||
|
||||
@Override
|
||||
public int compare(HistoryItem lhs, HistoryItem rhs) {
|
||||
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;
|
||||
@@ -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,31 @@ 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 {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
asset.open(BLOCKED_DOMAINS_LIST_FILE_NAME)));
|
||||
String line;
|
||||
@@ -73,16 +77,16 @@ public class AdBlock {
|
||||
}
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
@@ -104,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);
|
||||
@@ -129,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;
|
||||
@@ -172,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,6 +18,8 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import acr.browser.lightning.constant.Constants;
|
||||
|
||||
public class IntentUtils {
|
||||
|
||||
private final Activity mActivity;
|
||||
@@ -23,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(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);
|
||||
@@ -39,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) {
|
||||
@@ -51,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 final 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
-24
@@ -1,48 +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.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 final I2PAndroidHelper mI2PHelper;
|
||||
private static boolean mI2PHelperBound;
|
||||
private static boolean mI2PProxyInitialized;
|
||||
private final PreferenceManager mPreferences;
|
||||
private static ProxyUtils mInstance;
|
||||
|
||||
private ProxyUtils(Context context) {
|
||||
mPreferences = PreferenceManager.getInstance();
|
||||
mI2PHelper = new I2PAndroidHelper(context.getApplicationContext());
|
||||
}
|
||||
@Inject PreferenceManager mPreferences;
|
||||
@Inject I2PAndroidHelper mI2PHelper;
|
||||
@Inject Bus mBus;
|
||||
|
||||
public static ProxyUtils getInstance() {
|
||||
if (mInstance == null) {
|
||||
mInstance = new ProxyUtils(BrowserApp.getAppContext());
|
||||
}
|
||||
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);
|
||||
@@ -105,7 +103,7 @@ public class ProxyUtils {
|
||||
/*
|
||||
* Initialize WebKit Proxying
|
||||
*/
|
||||
private void initializeProxy(Activity activity) {
|
||||
private void initializeProxy(@NonNull Activity activity) {
|
||||
String host;
|
||||
int port;
|
||||
|
||||
@@ -143,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;
|
||||
}
|
||||
}
|
||||
@@ -157,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 {
|
||||
@@ -190,7 +188,7 @@ public class ProxyUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static 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)) {
|
||||
@@ -200,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);
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -12,9 +13,9 @@ import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
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;
|
||||
@@ -44,20 +45,28 @@ public class ThemeUtils {
|
||||
return color;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int getIconLightThemeColor(@NonNull Context context) {
|
||||
return ContextCompat.getColor(context, R.color.icon_light_theme);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int getIconDarkThemeColor(@NonNull Context context) {
|
||||
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);
|
||||
@@ -70,34 +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 = ContextCompat.getDrawable(context, res);
|
||||
if (drawable == null)
|
||||
return null;
|
||||
drawable.mutate();
|
||||
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Drawable getLightThemedDrawable(@NonNull Context context, @DrawableRes int res) {
|
||||
final Drawable drawable = ContextCompat.getDrawable(context, 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) {
|
||||
final int color = (dark) ? ContextCompat.getColor(context, R.color.selected_dark) :
|
||||
@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 getTextColor(Context context) {
|
||||
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,12 +15,19 @@
|
||||
*/
|
||||
package acr.browser.lightning.utils;
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -28,7 +35,7 @@ public class UrlUtils {
|
||||
private static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
|
||||
"(?i)" + // switch on case insensitive matching
|
||||
'(' + // begin group for schema
|
||||
"(?:http|https|file):\\/\\/" +
|
||||
"(?:http|https|file)://" +
|
||||
"|(?:inline|data|about|javascript):" +
|
||||
"|(?:.*:.*@)" +
|
||||
')' +
|
||||
@@ -53,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()) {
|
||||
@@ -74,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);
|
||||
@@ -99,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
|
||||
@@ -135,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 "";
|
||||
}
|
||||
@@ -145,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,16 +20,23 @@ import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Shader;
|
||||
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.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;
|
||||
@@ -37,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);
|
||||
@@ -61,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) {
|
||||
@@ -76,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);
|
||||
@@ -127,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();
|
||||
|
||||
@@ -148,7 +220,7 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
private 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) {
|
||||
@@ -169,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()
|
||||
@@ -230,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);
|
||||
@@ -249,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 {
|
||||
@@ -259,6 +331,24 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the trapezoid background for the horizontal tabs on a canvas object using
|
||||
* the specified color.
|
||||
@@ -266,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);
|
||||
@@ -297,4 +387,34 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package acr.browser.lightning.utils;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
import android.webkit.WebIconDatabase;
|
||||
@@ -33,8 +34,8 @@ public class WebUtils {
|
||||
WebStorage.getInstance().deleteAllData();
|
||||
}
|
||||
|
||||
public static void clearHistory(@NonNull Context context) {
|
||||
HistoryDatabase.getInstance().deleteHistory();
|
||||
public static void clearHistory(@NonNull Context context, @NonNull HistoryDatabase historyDatabase) {
|
||||
historyDatabase.deleteHistory();
|
||||
WebViewDatabase m = WebViewDatabase.getInstance(context);
|
||||
m.clearFormData();
|
||||
m.clearHttpAuthUsernamePassword();
|
||||
@@ -47,7 +48,7 @@ public class WebUtils {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 156 B |
@@ -1,6 +1,7 @@
|
||||
<!-- Copyright 2014 ACR Development -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
android:id="@+id/main_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@null"
|
||||
@@ -18,30 +19,31 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<include layout="@layout/browser_content" />
|
||||
<include layout="@layout/browser_content"/>
|
||||
|
||||
<include layout="@layout/search_interface" />
|
||||
<include layout="@layout/search_interface"/>
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/tab_drawer" />
|
||||
<FrameLayout
|
||||
android:id="@+id/left_drawer"
|
||||
android:layout_width="@dimen/navigation_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="?attr/drawerBackground"
|
||||
android:fitsSystemWindows="true"
|
||||
android:weightSum="1"/>
|
||||
|
||||
<FrameLayout
|
||||
android:weightSum="1"
|
||||
android:layout_gravity="end"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/right_drawer"
|
||||
android:background="?attr/drawerBackground"
|
||||
android:layout_width="@dimen/navigation_width"
|
||||
android:layout_height="match_parent">
|
||||
<fragment
|
||||
android:id="@+id/bookmark_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
class="acr.browser.lightning.fragment.BookmarksFragment" />
|
||||
</FrameLayout>
|
||||
<!-- include layout="@layout/bookmark_drawer" / -->
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="?attr/drawerBackground"
|
||||
android:fitsSystemWindows="true"
|
||||
android:weightSum="1"/>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/drawerBackground"
|
||||
android:clickable="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
@@ -26,7 +28,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="Favicon"
|
||||
android:src="@drawable/ic_action_star" />
|
||||
android:src="@drawable/ic_action_star"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
@@ -38,7 +40,7 @@
|
||||
android:maxLines="1"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:text="@string/action_bookmarks"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
@@ -48,7 +50,7 @@
|
||||
android:layout_weight="1"
|
||||
android:divider="@null"
|
||||
android:dividerHeight="0dp"
|
||||
android:listSelector="?attr/listBackground" />
|
||||
android:listSelector="?attr/listBackground"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -76,7 +78,7 @@
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:src="@drawable/ic_action_desktop" />
|
||||
android:src="@drawable/ic_action_desktop"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -97,7 +99,7 @@
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:src="@drawable/ic_action_star" />
|
||||
android:src="@drawable/ic_action_star"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -118,7 +120,7 @@
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:src="@drawable/ic_action_reading" />
|
||||
android:src="@drawable/ic_action_reading"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout
|
||||
android:id="@+id/content_frame"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/primary_color" />
|
||||
android:background="@color/primary_color"
|
||||
android:clipChildren="true"/>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="center">
|
||||
|
||||
<AutoCompleteTextView
|
||||
<acr.browser.lightning.view.SearchView
|
||||
android:id="@+id/search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -29,7 +29,6 @@
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="5dp"
|
||||
android:paddingTop="1dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/gray_dark"
|
||||
android:textColorHint="@color/hint_text"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_next"
|
||||
android:id="@+id/button_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
@@ -44,7 +44,7 @@
|
||||
android:background="?android:attr/dividerVertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_back"
|
||||
android:id="@+id/button_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/left_drawer"
|
||||
android:layout_width="@dimen/navigation_width"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="?attr/drawerBackground"
|
||||
android:clickable="true"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
@@ -29,7 +27,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_new_tab"
|
||||
android:src="@drawable/ic_action_tabs" />
|
||||
android:src="@drawable/ic_action_tabs"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
@@ -41,16 +39,17 @@
|
||||
android:maxLines="1"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:text="@string/tabs"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"/>
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/left_drawer_list"
|
||||
android:id="@+id/tabs_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:divider="@null"
|
||||
android:dividerHeight="0dp" />
|
||||
android:dividerHeight="0dp"
|
||||
android:overScrollMode="ifContentScrolls"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -74,24 +73,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_back"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/new_tab_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/actionBarItemBackground"
|
||||
android:clickable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_new_tab"
|
||||
android:src="@drawable/ic_action_plus" />
|
||||
android:src="@drawable/ic_action_back"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -108,7 +90,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_homepage"
|
||||
android:src="@drawable/ic_action_home" />
|
||||
android:src="@drawable/ic_action_home"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -125,7 +107,24 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_forward"
|
||||
android:src="@drawable/ic_action_forward" />
|
||||
android:src="@drawable/ic_action_forward"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/new_tab_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/actionBarItemBackground"
|
||||
android:clickable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/action_new_tab"
|
||||
android:src="@drawable/ic_action_plus"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,61 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tab_item_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
<LinearLayout android:id="@+id/tab_item_background"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="?attr/listBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/listBackground"
|
||||
<ImageView
|
||||
android:id="@+id/faviconTab"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:contentDescription="Favicon"
|
||||
android:gravity="center_vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textTab"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="1">
|
||||
android:maxLines="1"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/deleteAction"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="4dp"
|
||||
android:background="?attr/actionBarItemBackground"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/faviconTab"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:contentDescription="Favicon"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textTab"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/deleteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/deleteAction"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="4dp"
|
||||
android:background="?attr/actionBarItemBackground"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/deleteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="Delete Tab"
|
||||
android:src="@drawable/ic_action_delete" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="Delete Tab"
|
||||
android:src="@drawable/ic_action_delete"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:background="@color/black"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="1">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/tabs_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:fadingEdge="horizontal"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/new_tab_button"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?actionBarItemBackground"
|
||||
android:src="@drawable/ic_action_plus"/>
|
||||
</LinearLayout>
|
||||
|
||||
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