Browse Source

Merge pull request #597 from anthonycr/dev

Version 4.5.1
master v4.5.1
Anthony Restaino 8 years ago committed by GitHub
parent
commit
e00bb88366
  1. 7
      .travis.yml
  2. 12
      CHANGELOG.md
  3. 11
      README.md
  4. 15
      app/build.gradle
  5. 121
      app/src/LightningPlus/assets/hosts.txt
  6. 18
      app/src/main/AndroidManifest.xml
  7. 8
      app/src/main/java/acr/browser/lightning/BrowserApp.java
  8. 4
      app/src/main/java/acr/browser/lightning/IncognitoActivity.java
  9. 22
      app/src/main/java/acr/browser/lightning/MainActivity.java
  10. 4
      app/src/main/java/acr/browser/lightning/activity/AppCompatPreferenceActivity.java
  11. 79
      app/src/main/java/acr/browser/lightning/adblock/AdBlock.java
  12. 29
      app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java
  13. 5
      app/src/main/java/acr/browser/lightning/browser/BrowserView.java
  14. 71
      app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.java
  15. 26
      app/src/main/java/acr/browser/lightning/browser/TabsManager.java
  16. 225
      app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.java
  17. 7
      app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.java
  18. 2
      app/src/main/java/acr/browser/lightning/browser/bookmark/BookmarkUiModel.java
  19. 18
      app/src/main/java/acr/browser/lightning/browser/fragment/BookmarksFragment.java
  20. 24
      app/src/main/java/acr/browser/lightning/browser/fragment/TabsFragment.java
  21. 2
      app/src/main/java/acr/browser/lightning/browser/fragment/anim/HorizontalItemAnimator.java
  22. 2
      app/src/main/java/acr/browser/lightning/browser/fragment/anim/VerticalItemAnimator.java
  23. 151
      app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java
  24. 6
      app/src/main/java/acr/browser/lightning/constant/Constants.java
  25. 31
      app/src/main/java/acr/browser/lightning/constant/DownloadsPage.java
  26. 21
      app/src/main/java/acr/browser/lightning/constant/HistoryPage.java
  27. 133
      app/src/main/java/acr/browser/lightning/constant/StartPage.java
  28. 4
      app/src/main/java/acr/browser/lightning/controller/UIController.java
  29. 44
      app/src/main/java/acr/browser/lightning/database/HistoryItem.java
  30. 113
      app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java
  31. 30
      app/src/main/java/acr/browser/lightning/database/downloads/DownloadItem.java
  32. 2
      app/src/main/java/acr/browser/lightning/database/downloads/DownloadsDatabase.java
  33. 165
      app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java
  34. 80
      app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java
  35. 38
      app/src/main/java/acr/browser/lightning/di/AppComponent.java
  36. 24
      app/src/main/java/acr/browser/lightning/di/AppModule.java
  37. 6
      app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.java
  38. 34
      app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
  39. 145
      app/src/main/java/acr/browser/lightning/download/DownloadHandler.java
  40. 19
      app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java
  41. 72
      app/src/main/java/acr/browser/lightning/favicon/FaviconModel.java
  42. 4
      app/src/main/java/acr/browser/lightning/favicon/FaviconUtils.java
  43. 110
      app/src/main/java/acr/browser/lightning/favicon/ImageFetcher.java
  44. 10
      app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.java
  45. 4
      app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java
  46. 4
      app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java
  47. 6
      app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java
  48. 112
      app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.java
  49. 18
      app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java
  50. 67
      app/src/main/java/acr/browser/lightning/search/SuggestionsManager.java
  51. 46
      app/src/main/java/acr/browser/lightning/search/SuggestionsManager.kt
  52. 13
      app/src/main/java/acr/browser/lightning/search/engine/AskSearch.kt
  53. 15
      app/src/main/java/acr/browser/lightning/search/engine/BaiduSearch.kt
  54. 15
      app/src/main/java/acr/browser/lightning/search/engine/BaseSearchEngine.kt
  55. 16
      app/src/main/java/acr/browser/lightning/search/engine/BingSearch.kt
  56. 12
      app/src/main/java/acr/browser/lightning/search/engine/CustomSearch.kt
  57. 15
      app/src/main/java/acr/browser/lightning/search/engine/DuckLiteSearch.kt
  58. 15
      app/src/main/java/acr/browser/lightning/search/engine/DuckSearch.kt
  59. 15
      app/src/main/java/acr/browser/lightning/search/engine/GoogleSearch.kt
  60. 13
      app/src/main/java/acr/browser/lightning/search/engine/StartPageMobileSearch.kt
  61. 13
      app/src/main/java/acr/browser/lightning/search/engine/StartPageSearch.kt
  62. 16
      app/src/main/java/acr/browser/lightning/search/engine/YahooSearch.kt
  63. 16
      app/src/main/java/acr/browser/lightning/search/engine/YandexSearch.kt
  64. 22
      app/src/main/java/acr/browser/lightning/search/suggestions/BaiduSuggestionsModel.java
  65. 60
      app/src/main/java/acr/browser/lightning/search/suggestions/BaseSuggestionsModel.java
  66. 9
      app/src/main/java/acr/browser/lightning/search/suggestions/DuckSuggestionsModel.java
  67. 13
      app/src/main/java/acr/browser/lightning/search/suggestions/GoogleSuggestionsModel.java
  68. 20
      app/src/main/java/acr/browser/lightning/settings/activity/SettingsActivity.java
  69. 5
      app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.java
  70. 16
      app/src/main/java/acr/browser/lightning/settings/fragment/AboutSettingsFragment.java
  71. 2
      app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.java
  72. 12
      app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.java
  73. 4
      app/src/main/java/acr/browser/lightning/settings/fragment/DebugSettingsFragment.java
  74. 4
      app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.java
  75. 123
      app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.java
  76. 4
      app/src/main/java/acr/browser/lightning/settings/fragment/LightningPreferenceFragment.java
  77. 12
      app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.java
  78. 69
      app/src/main/java/acr/browser/lightning/utils/DrawableUtils.java
  79. 86
      app/src/main/java/acr/browser/lightning/utils/FileUtils.java
  80. 2
      app/src/main/java/acr/browser/lightning/utils/IntentUtils.java
  81. 2
      app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java
  82. 12
      app/src/main/java/acr/browser/lightning/utils/ResourceUtils.java
  83. 10
      app/src/main/java/acr/browser/lightning/utils/StringBuilderUtils.java
  84. 39
      app/src/main/java/acr/browser/lightning/utils/Utils.java
  85. 4
      app/src/main/java/acr/browser/lightning/utils/WebUtils.java
  86. 42
      app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java
  87. 51
      app/src/main/java/acr/browser/lightning/view/LightningView.java
  88. 29
      app/src/main/java/acr/browser/lightning/view/LightningWebClient.java
  89. 2
      app/src/main/res/layout/activity_main.xml
  90. 4
      app/src/main/res/layout/bookmark_list_item.xml
  91. 8
      app/src/main/res/raw/default_bookmarks.dat
  92. 5
      app/src/main/res/values-de/strings.xml
  93. 2
      app/src/main/res/values-el/strings.xml
  94. 2
      app/src/main/res/values-es/strings.xml
  95. 2
      app/src/main/res/values-fr/strings.xml
  96. 2
      app/src/main/res/values-hu/strings.xml
  97. 4
      app/src/main/res/values-it/strings.xml
  98. 28
      app/src/main/res/values-ja/strings.xml
  99. 2
      app/src/main/res/values-ko/strings.xml
  100. 2
      app/src/main/res/values-nl/strings.xml
  101. Some files were not shown because too many files have changed in this diff Show More

7
.travis.yml

@ -5,8 +5,8 @@ jdk:
android: android:
components: components:
- tools - tools
- build-tools-25.0.3 - build-tools-26.0.0
- android-25 - android-26
- extra-android-support - extra-android-support
- extra-android-m2repository - extra-android-m2repository
licenses: licenses:
@ -18,4 +18,5 @@ before_install:
install: install:
- ./gradlew - ./gradlew
script: script:
- ./gradlew assembleDebug --stacktrace - ./gradlew :app:assembleDebug --stacktrace
- ./gradlew :app:test --stacktrace

12
CHANGELOG.md

@ -1,6 +1,18 @@
Change Log Change Log
========== ==========
Version 4.5.1 *(2017-06-28)*
----------------------------
- Fixed bug with folders disappearing on bookmark homepage
- Updated history page
- Updated bookmark page
- Updating target to Android O
- Updating default bookmark favicons
- Fixed occasional bug with bookmark long press
- Updated downloads page design
- Enhanced keyboard shortcuts
- Fixed bug in google search suggestions for certain languages
Version 4.5.0 *(2017-06-08)* Version 4.5.0 *(2017-06-08)*
---------------------------- ----------------------------
- Translation updates - Translation updates

11
README.md

@ -4,13 +4,10 @@
![](ic_launcher_small.png) ![](ic_launcher_small.png)
#### Download #### Download
* [Download APK from here](https://github.com/anthonycr/Lightning-Browser/releases) [<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
* [Download from F-Droid](https://f-droid.org/repository/browse/?fdfilter=lightning&fdid=acr.browser.lightning) height="80">](https://f-droid.org/app/acr.browser.lightning) [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play" height="80">](https://play.google.com/store/apps/details?id=acr.browser.lightning)
* [Download Free from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones)
* [Download Paid from Google Play](https://play.google.com/store/apps/details?id=acr.browser.lightning)
#### Master Branch #### Master Branch
* [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=master)](https://travis-ci.org/anthonycr/Lightning-Browser) * [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=master)](https://travis-ci.org/anthonycr/Lightning-Browser)

15
app/build.gradle

@ -1,13 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.getkeepsafe.dexcount'
android { android {
compileSdkVersion 25 compileSdkVersion project.targetSdkVersion
buildToolsVersion '25.0.3' buildToolsVersion project.buildToolsVersion
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion project.minSdkVersion
targetSdkVersion 25 targetSdkVersion project.targetSdkVersion
versionName project.versionName versionName project.versionName
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@ -63,9 +64,10 @@ dexcount {
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12'
// support libraries // support libraries
def supportLibVersion = '25.3.1' def supportLibVersion = '25.4.0'
compile "com.android.support:palette-v7:$supportLibVersion" compile "com.android.support:palette-v7:$supportLibVersion"
compile "com.android.support:appcompat-v7:$supportLibVersion" compile "com.android.support:appcompat-v7:$supportLibVersion"
compile "com.android.support:design:$supportLibVersion" compile "com.android.support:design:$supportLibVersion"
@ -107,4 +109,7 @@ dependencies {
def leakCanaryVersion = '1.5.1' def leakCanaryVersion = '1.5.1'
debugCompile "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" debugCompile "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
// Kotlin
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
} }

121
app/src/LightningPlus/assets/hosts.txt

@ -1033,6 +1033,7 @@
127.0.0.1 4228414.fls.doubleclick.net 127.0.0.1 4228414.fls.doubleclick.net
127.0.0.1 4235225.fls.doubleclick.net 127.0.0.1 4235225.fls.doubleclick.net
127.0.0.1 4236808.fls.doubleclick.net 127.0.0.1 4236808.fls.doubleclick.net
127.0.0.1 4294919531.kt.mixmarket.biz
127.0.0.1 4299553.fls.doubleclick.net 127.0.0.1 4299553.fls.doubleclick.net
127.0.0.1 43.6.87.194.dynamic.dol.ru 127.0.0.1 43.6.87.194.dynamic.dol.ru
127.0.0.1 4331660.fls.doubleclick.net 127.0.0.1 4331660.fls.doubleclick.net
@ -14754,6 +14755,7 @@
127.0.0.1 ad1.hpg.com.br 127.0.0.1 ad1.hpg.com.br
127.0.0.1 ad1.icorp.net 127.0.0.1 ad1.icorp.net
127.0.0.1 ad1.kde.cz 127.0.0.1 ad1.kde.cz
127.0.0.1 ad1.nend.net
127.0.0.1 ad1.pamedia.com.au 127.0.0.1 ad1.pamedia.com.au
127.0.0.1 ad1.paycount.com 127.0.0.1 ad1.paycount.com
127.0.0.1 ad1.primorye.ru 127.0.0.1 ad1.primorye.ru
@ -15137,6 +15139,7 @@
127.0.0.1 ad2.doublepimp.com 127.0.0.1 ad2.doublepimp.com
127.0.0.1 ad2.ero-advertising.com 127.0.0.1 ad2.ero-advertising.com
127.0.0.1 ad2.firehousezone.com 127.0.0.1 ad2.firehousezone.com
127.0.0.1 ad2.fivecdm.com
127.0.0.1 ad2.hpg.com.br 127.0.0.1 ad2.hpg.com.br
127.0.0.1 ad2.ip.ro 127.0.0.1 ad2.ip.ro
127.0.0.1 ad2.linxcz.cz 127.0.0.1 ad2.linxcz.cz
@ -16026,6 +16029,7 @@
127.0.0.1 addeppo.com 127.0.0.1 addeppo.com
127.0.0.1 addesktop.com 127.0.0.1 addesktop.com
127.0.0.1 addfreestats.com 127.0.0.1 addfreestats.com
127.0.0.1 addiliate.com
127.0.0.1 addiply.com 127.0.0.1 addiply.com
127.0.0.1 addltest.timesink.com 127.0.0.1 addltest.timesink.com
127.0.0.1 addoer.com 127.0.0.1 addoer.com
@ -16055,6 +16059,8 @@
127.0.0.1 adespresso.com 127.0.0.1 adespresso.com
127.0.0.1 adex.adchakra.net 127.0.0.1 adex.adchakra.net
127.0.0.1 adex.admailtiser.com 127.0.0.1 adex.admailtiser.com
127.0.0.1 adexc.net
127.0.0.1 adexce.net
127.0.0.1 adexcite.com 127.0.0.1 adexcite.com
127.0.0.1 adexit.com 127.0.0.1 adexit.com
127.0.0.1 adexit.de 127.0.0.1 adexit.de
@ -16197,6 +16203,7 @@
127.0.0.1 adg.bzgint.com 127.0.0.1 adg.bzgint.com
127.0.0.1 adgaem.go2cloud.org 127.0.0.1 adgaem.go2cloud.org
127.0.0.1 adgalax.com 127.0.0.1 adgalax.com
127.0.0.1 adgale.com
127.0.0.1 adgardener.com 127.0.0.1 adgardener.com
127.0.0.1 adgatemedia.com 127.0.0.1 adgatemedia.com
127.0.0.1 adgatemedia.go2cloud.org 127.0.0.1 adgatemedia.go2cloud.org
@ -16537,6 +16544,7 @@
127.0.0.1 adrspain.go2cloud.org 127.0.0.1 adrspain.go2cloud.org
127.0.0.1 adrtrklnk.com 127.0.0.1 adrtrklnk.com
127.0.0.1 adrunner.superstats.com 127.0.0.1 adrunner.superstats.com
127.0.0.1 adrunnr.com
127.0.0.1 ads-03.tor.focusin.ads.targetnet.com 127.0.0.1 ads-03.tor.focusin.ads.targetnet.com
127.0.0.1 ads-aa.wunderground.com 127.0.0.1 ads-aa.wunderground.com
127.0.0.1 ads-au.onelouder.com 127.0.0.1 ads-au.onelouder.com
@ -16838,6 +16846,7 @@
127.0.0.1 ads.dfiles.ru 127.0.0.1 ads.dfiles.ru
127.0.0.1 ads.dgonn.com 127.0.0.1 ads.dgonn.com
127.0.0.1 ads.dhakatribune.com 127.0.0.1 ads.dhakatribune.com
127.0.0.1 ads.diamonds
127.0.0.1 ads.digital-digest.com 127.0.0.1 ads.digital-digest.com
127.0.0.1 ads.digitalhealthcare.com 127.0.0.1 ads.digitalhealthcare.com
127.0.0.1 ads.digitalmedianet.com 127.0.0.1 ads.digitalmedianet.com
@ -17074,6 +17083,7 @@
127.0.0.1 ads.keywordblocks.com 127.0.0.1 ads.keywordblocks.com
127.0.0.1 ads.kinobox.cz 127.0.0.1 ads.kinobox.cz
127.0.0.1 ads.kinxxx.com 127.0.0.1 ads.kinxxx.com
127.0.0.1 ads.kiosked.com
127.0.0.1 ads.kizu.org 127.0.0.1 ads.kizu.org
127.0.0.1 ads.klixxx.com 127.0.0.1 ads.klixxx.com
127.0.0.1 ads.komli.com 127.0.0.1 ads.komli.com
@ -17720,6 +17730,7 @@
127.0.0.1 ads.webmastersdirect.com 127.0.0.1 ads.webmastersdirect.com
127.0.0.1 ads.webmd.com 127.0.0.1 ads.webmd.com
127.0.0.1 ads.webme.com 127.0.0.1 ads.webme.com
127.0.0.1 ads.webseoanalytics.com
127.0.0.1 ads.webshots.com 127.0.0.1 ads.webshots.com
127.0.0.1 ads.website-guru.com 127.0.0.1 ads.website-guru.com
127.0.0.1 ads.whaleads.com 127.0.0.1 ads.whaleads.com
@ -18294,6 +18305,7 @@
127.0.0.1 adserver.adreactor.com 127.0.0.1 adserver.adreactor.com
127.0.0.1 adserver.ads.com.ph 127.0.0.1 adserver.ads.com.ph
127.0.0.1 adserver.ads360.com 127.0.0.1 adserver.ads360.com
127.0.0.1 adserver.adtech.advertising.com
127.0.0.1 adserver.adtech.de 127.0.0.1 adserver.adtech.de
127.0.0.1 adserver.adtech.de.1754.9018.302br.net 127.0.0.1 adserver.adtech.de.1754.9018.302br.net
127.0.0.1 adserver.adtech.de.1756.9018.302br.net 127.0.0.1 adserver.adtech.de.1756.9018.302br.net
@ -18441,6 +18453,7 @@
127.0.0.1 adserver.trellian.com 127.0.0.1 adserver.trellian.com
127.0.0.1 adserver.tweakers.net 127.0.0.1 adserver.tweakers.net
127.0.0.1 adserver.ubiyoo.com 127.0.0.1 adserver.ubiyoo.com
127.0.0.1 adserver.unityads.unity3d.com
127.0.0.1 adserver.veruta.com 127.0.0.1 adserver.veruta.com
127.0.0.1 adserver.virgin.net 127.0.0.1 adserver.virgin.net
127.0.0.1 adserver.weakgame.com 127.0.0.1 adserver.weakgame.com
@ -19197,7 +19210,6 @@
127.0.0.1 affiliatenetwork.com 127.0.0.1 affiliatenetwork.com
127.0.0.1 affiliateprogram.keywordspy.com 127.0.0.1 affiliateprogram.keywordspy.com
127.0.0.1 affiliatequality.com 127.0.0.1 affiliatequality.com
127.0.0.1 affiliates-cdn.mozilla.org
127.0.0.1 affiliates.5dimes.com 127.0.0.1 affiliates.5dimes.com
127.0.0.1 affiliates.a2hosting.com 127.0.0.1 affiliates.a2hosting.com
127.0.0.1 affiliates.about.com 127.0.0.1 affiliates.about.com
@ -19384,6 +19396,7 @@
127.0.0.1 akamai.bizrate.com 127.0.0.1 akamai.bizrate.com
127.0.0.1 akamai.smartadserver.com 127.0.0.1 akamai.smartadserver.com
127.0.0.1 akillimeyveler.reklamagaci.com 127.0.0.1 akillimeyveler.reklamagaci.com
127.0.0.1 akm.playnow.guru
127.0.0.1 akmnetwork.com 127.0.0.1 akmnetwork.com
127.0.0.1 aknoj.voluumtrk.com 127.0.0.1 aknoj.voluumtrk.com
127.0.0.1 aksb-a.akamaihd.net 127.0.0.1 aksb-a.akamaihd.net
@ -19731,7 +19744,6 @@
127.0.0.1 antoinette-grabowski.us 127.0.0.1 antoinette-grabowski.us
127.0.0.1 antoinette-hanlon.us 127.0.0.1 antoinette-hanlon.us
127.0.0.1 antoinette-meisner.us 127.0.0.1 antoinette-meisner.us
127.0.0.1 anx.mindspark.com
127.0.0.1 any.gs 127.0.0.1 any.gs
127.0.0.1 anycast.fw.adsafeprotected.com 127.0.0.1 anycast.fw.adsafeprotected.com
127.0.0.1 anycast.pixel.adsafeprotected.com 127.0.0.1 anycast.pixel.adsafeprotected.com
@ -19886,7 +19898,6 @@
127.0.0.1 api.vungle.com 127.0.0.1 api.vungle.com
127.0.0.1 api.w3i.com 127.0.0.1 api.w3i.com
127.0.0.1 api.widgetbucks.com 127.0.0.1 api.widgetbucks.com
127.0.0.1 api.wipmania.com
127.0.0.1 api.xdirectx.com 127.0.0.1 api.xdirectx.com
127.0.0.1 api.yeahmobi.com 127.0.0.1 api.yeahmobi.com
127.0.0.1 api.yieldkit.com 127.0.0.1 api.yieldkit.com
@ -20187,6 +20198,7 @@
127.0.0.1 assets.servedby-buysellads.com 127.0.0.1 assets.servedby-buysellads.com
127.0.0.1 assets.sharethrough.com 127.0.0.1 assets.sharethrough.com
127.0.0.1 assets.tapad.com 127.0.0.1 assets.tapad.com
127.0.0.1 assistonglobal.com
127.0.0.1 assoc-amazon.co.uk 127.0.0.1 assoc-amazon.co.uk
127.0.0.1 associatedcontent.112.2o7.net 127.0.0.1 associatedcontent.112.2o7.net
127.0.0.1 associateprograms.com 127.0.0.1 associateprograms.com
@ -20510,7 +20522,6 @@
127.0.0.1 bam-3.nr-data.net 127.0.0.1 bam-3.nr-data.net
127.0.0.1 bam-4.nr-data.net 127.0.0.1 bam-4.nr-data.net
127.0.0.1 bam-5.nr-data.net 127.0.0.1 bam-5.nr-data.net
127.0.0.1 bam.nr-data.net
127.0.0.1 ban.krooncasino.com 127.0.0.1 ban.krooncasino.com
127.0.0.1 ban.promotools.biz 127.0.0.1 ban.promotools.biz
127.0.0.1 ban3ers.ero-advertising.com 127.0.0.1 ban3ers.ero-advertising.com
@ -21500,6 +21511,7 @@
127.0.0.1 bbpntg3.homestead.com 127.0.0.1 bbpntg3.homestead.com
127.0.0.1 bbs.duba.net 127.0.0.1 bbs.duba.net
127.0.0.1 bbvj6.voluumtrk.com 127.0.0.1 bbvj6.voluumtrk.com
127.0.0.1 bc2.fivecdm.com
127.0.0.1 bc84-88b8-96b7-6515.reporo.net 127.0.0.1 bc84-88b8-96b7-6515.reporo.net
127.0.0.1 bcanalytics.bigcommerce.com 127.0.0.1 bcanalytics.bigcommerce.com
127.0.0.1 bcast.pw 127.0.0.1 bcast.pw
@ -21706,7 +21718,6 @@
127.0.0.1 blog.adspaces.ero-advertising.com 127.0.0.1 blog.adspaces.ero-advertising.com
127.0.0.1 blog.chartboost.com 127.0.0.1 blog.chartboost.com
127.0.0.1 blog.data.ero-advertising.com 127.0.0.1 blog.data.ero-advertising.com
127.0.0.1 blog.disqus.com
127.0.0.1 blog.duba.net 127.0.0.1 blog.duba.net
127.0.0.1 blog.ero-advertising.com 127.0.0.1 blog.ero-advertising.com
127.0.0.1 blog.intext.ero-advertising.com 127.0.0.1 blog.intext.ero-advertising.com
@ -23301,6 +23312,7 @@
127.0.0.1 c.aol.com 127.0.0.1 c.aol.com
127.0.0.1 c.apple.com 127.0.0.1 c.apple.com
127.0.0.1 c.atdmt.com 127.0.0.1 c.atdmt.com
127.0.0.1 c.ato.mx
127.0.0.1 c.baidu.com 127.0.0.1 c.baidu.com
127.0.0.1 c.betrad.com 127.0.0.1 c.betrad.com
127.0.0.1 c.bigmir.net 127.0.0.1 c.bigmir.net
@ -23365,6 +23377,7 @@
127.0.0.1 c.thanksearch.com 127.0.0.1 c.thanksearch.com
127.0.0.1 c.tracking.gamigoads.com 127.0.0.1 c.tracking.gamigoads.com
127.0.0.1 c.trffrcmrd.com 127.0.0.1 c.trffrcmrd.com
127.0.0.1 c.us1.dyntrk.com
127.0.0.1 c.viewsecure.net 127.0.0.1 c.viewsecure.net
127.0.0.1 c.vindicosuite.com 127.0.0.1 c.vindicosuite.com
127.0.0.1 c.vrvm.com 127.0.0.1 c.vrvm.com
@ -23574,7 +23587,6 @@
127.0.0.1 campaigns.f2.com.au 127.0.0.1 campaigns.f2.com.au
127.0.0.1 campaigns.interclick.com 127.0.0.1 campaigns.interclick.com
127.0.0.1 campaigns.rioseo.com 127.0.0.1 campaigns.rioseo.com
127.0.0.1 campaigns.services.mozilla.com
127.0.0.1 campaigntracking01.com 127.0.0.1 campaigntracking01.com
127.0.0.1 campf.voluumtrk.com 127.0.0.1 campf.voluumtrk.com
127.0.0.1 camsiteonline.wiredcircular.com 127.0.0.1 camsiteonline.wiredcircular.com
@ -23694,6 +23706,7 @@
127.0.0.1 cbvtest.2cnt.net 127.0.0.1 cbvtest.2cnt.net
127.0.0.1 cbx.net 127.0.0.1 cbx.net
127.0.0.1 cc.amazingcounters.com 127.0.0.1 cc.amazingcounters.com
127.0.0.1 cc.chango.com
127.0.0.1 cc.connextra.com 127.0.0.1 cc.connextra.com
127.0.0.1 cc.zeit.de 127.0.0.1 cc.zeit.de
127.0.0.1 cc03-41cf-c2ea-3a84.reporo.net 127.0.0.1 cc03-41cf-c2ea-3a84.reporo.net
@ -23714,6 +23727,7 @@
127.0.0.1 cdn-0.pics.dvdcdn.com 127.0.0.1 cdn-0.pics.dvdcdn.com
127.0.0.1 cdn-akamai.mookie1.com 127.0.0.1 cdn-akamai.mookie1.com
127.0.0.1 cdn-ci34.actonsoftware.com 127.0.0.1 cdn-ci34.actonsoftware.com
127.0.0.1 cdn-highwinds.unityads.unity3d.com
127.0.0.1 cdn-i.dmdentertainment.com 127.0.0.1 cdn-i.dmdentertainment.com
127.0.0.1 cdn-static.liverail.com 127.0.0.1 cdn-static.liverail.com
127.0.0.1 cdn-tags.mmondi.com 127.0.0.1 cdn-tags.mmondi.com
@ -24245,6 +24259,7 @@
127.0.0.1 choice.demdex.net 127.0.0.1 choice.demdex.net
127.0.0.1 choice.microsoft.com 127.0.0.1 choice.microsoft.com
127.0.0.1 choices.truste.com 127.0.0.1 choices.truste.com
127.0.0.1 chokoladsrv.net
127.0.0.1 chope-co.b.appier.net 127.0.0.1 chope-co.b.appier.net
127.0.0.1 christie-hanlon.us 127.0.0.1 christie-hanlon.us
127.0.0.1 christie-pearlman.us 127.0.0.1 christie-pearlman.us
@ -26626,6 +26641,7 @@
127.0.0.1 d.refinedads.com 127.0.0.1 d.refinedads.com
127.0.0.1 d.rmgserving.com 127.0.0.1 d.rmgserving.com
127.0.0.1 d.skimresources.com 127.0.0.1 d.skimresources.com
127.0.0.1 d.socdm.com
127.0.0.1 d.tds.adlabs.ru 127.0.0.1 d.tds.adlabs.ru
127.0.0.1 d.thanksearch.com 127.0.0.1 d.thanksearch.com
127.0.0.1 d.thelocal.com 127.0.0.1 d.thelocal.com
@ -27259,6 +27275,7 @@
127.0.0.1 dimpact.co.il 127.0.0.1 dimpact.co.il
127.0.0.1 dina-rish.us 127.0.0.1 dina-rish.us
127.0.0.1 dinclinx.com 127.0.0.1 dinclinx.com
127.0.0.1 dintandnesin.ru
127.0.0.1 direct-revenue.com 127.0.0.1 direct-revenue.com
127.0.0.1 direct-stats.com 127.0.0.1 direct-stats.com
127.0.0.1 direct-xxx-access.com 127.0.0.1 direct-xxx-access.com
@ -27477,7 +27494,6 @@
127.0.0.1 doubleclicks.me 127.0.0.1 doubleclicks.me
127.0.0.1 doug1izaerwt3.cloudfront.net 127.0.0.1 doug1izaerwt3.cloudfront.net
127.0.0.1 dowlatow.justclick.ru 127.0.0.1 dowlatow.justclick.ru
127.0.0.1 download-performance.com
127.0.0.1 download.akamaitools.com.edgesuite.net 127.0.0.1 download.akamaitools.com.edgesuite.net
127.0.0.1 download.hitbox.com 127.0.0.1 download.hitbox.com
127.0.0.1 download.installnow.mobi 127.0.0.1 download.installnow.mobi
@ -27586,7 +27602,6 @@
127.0.0.1 dvdbeats.com 127.0.0.1 dvdbeats.com
127.0.0.1 dvnzx.voluumtrk.com 127.0.0.1 dvnzx.voluumtrk.com
127.0.0.1 dw-eu.com.com 127.0.0.1 dw-eu.com.com
127.0.0.1 dw.cbsi.com
127.0.0.1 dw.cbsi.com.cn 127.0.0.1 dw.cbsi.com.cn
127.0.0.1 dw.cnet.com 127.0.0.1 dw.cnet.com
127.0.0.1 dw.com.com 127.0.0.1 dw.com.com
@ -28022,6 +28037,7 @@
127.0.0.1 enews.bfast.com 127.0.0.1 enews.bfast.com
127.0.0.1 enewsletters.ziffdavisinternet.com 127.0.0.1 enewsletters.ziffdavisinternet.com
127.0.0.1 enfintrouver.com 127.0.0.1 enfintrouver.com
127.0.0.1 engage.morespeck.com
127.0.0.1 engagebdr.com 127.0.0.1 engagebdr.com
127.0.0.1 engine.4dsply.com 127.0.0.1 engine.4dsply.com
127.0.0.1 engine.a.redditmedia.com 127.0.0.1 engine.a.redditmedia.com
@ -28103,6 +28119,7 @@
127.0.0.1 epxkb8zz4ssdv7b.global.ssl.fastly.net 127.0.0.1 epxkb8zz4ssdv7b.global.ssl.fastly.net
127.0.0.1 eqads.com 127.0.0.1 eqads.com
127.0.0.1 eqgdx.voluumtrk.com 127.0.0.1 eqgdx.voluumtrk.com
127.0.0.1 eqs-redserver.appspot.com
127.0.0.1 equantum.com 127.0.0.1 equantum.com
127.0.0.1 equitymarketingsolutions.com 127.0.0.1 equitymarketingsolutions.com
127.0.0.1 eqx.smartadserver.com 127.0.0.1 eqx.smartadserver.com
@ -28258,6 +28275,7 @@
127.0.0.1 everyfreegift.com 127.0.0.1 everyfreegift.com
127.0.0.1 everyscape.com 127.0.0.1 everyscape.com
127.0.0.1 everythingbts.com 127.0.0.1 everythingbts.com
127.0.0.1 evewronsedent.ru
127.0.0.1 evidencecleanergold.com 127.0.0.1 evidencecleanergold.com
127.0.0.1 evisit.exeter.ac.uk 127.0.0.1 evisit.exeter.ac.uk
127.0.0.1 evisitcs2.com 127.0.0.1 evisitcs2.com
@ -28493,6 +28511,7 @@
127.0.0.1 feedshare.flipora.com 127.0.0.1 feedshare.flipora.com
127.0.0.1 felitb.rightinthebox.com 127.0.0.1 felitb.rightinthebox.com
127.0.0.1 femi9.voluumtrk.com 127.0.0.1 femi9.voluumtrk.com
127.0.0.1 fenix-translation.com.ua
127.0.0.1 ferdy.org 127.0.0.1 ferdy.org
127.0.0.1 fermakontenta.justclick.ru 127.0.0.1 fermakontenta.justclick.ru
127.0.0.1 ferrago.uk.intellitxt.com 127.0.0.1 ferrago.uk.intellitxt.com
@ -29471,7 +29490,6 @@
127.0.0.1 geo.hyperlinksecure.com 127.0.0.1 geo.hyperlinksecure.com
127.0.0.1 geo.ivwbox.de 127.0.0.1 geo.ivwbox.de
127.0.0.1 geo.metronews.ca 127.0.0.1 geo.metronews.ca
127.0.0.1 geo.mozilla.org
127.0.0.1 geo.mtvnn.com 127.0.0.1 geo.mtvnn.com
127.0.0.1 geo.offermatica.com 127.0.0.1 geo.offermatica.com
127.0.0.1 geo.q5media.net 127.0.0.1 geo.q5media.net
@ -30117,6 +30135,7 @@
127.0.0.1 hc.rapidshare.com 127.0.0.1 hc.rapidshare.com
127.0.0.1 hc1.humanclick.com 127.0.0.1 hc1.humanclick.com
127.0.0.1 hc2.humanclick.com 127.0.0.1 hc2.humanclick.com
127.0.0.1 hcharbach.de
127.0.0.1 hchrmain.112.2o7.net 127.0.0.1 hchrmain.112.2o7.net
127.0.0.1 hclrm.voluumtrk.com 127.0.0.1 hclrm.voluumtrk.com
127.0.0.1 hcu1u.voluumtrk.com 127.0.0.1 hcu1u.voluumtrk.com
@ -30153,6 +30172,7 @@
127.0.0.1 help.doubleclick.net 127.0.0.1 help.doubleclick.net
127.0.0.1 help.qualaroo.com 127.0.0.1 help.qualaroo.com
127.0.0.1 hernando.com 127.0.0.1 hernando.com
127.0.0.1 hetsedryropt.com
127.0.0.1 hexacash.com 127.0.0.1 hexacash.com
127.0.0.1 heywire.com 127.0.0.1 heywire.com
127.0.0.1 heyzap.com 127.0.0.1 heyzap.com
@ -30350,6 +30370,7 @@
127.0.0.1 hosting2.rts.lj.doublepimp.com 127.0.0.1 hosting2.rts.lj.doublepimp.com
127.0.0.1 hosting4.rts.fling.doublepimp.com 127.0.0.1 hosting4.rts.fling.doublepimp.com
127.0.0.1 hostlnks.com 127.0.0.1 hostlnks.com
127.0.0.1 hostpedia.ro
127.0.0.1 hostpost4.xyz 127.0.0.1 hostpost4.xyz
127.0.0.1 hot50.net 127.0.0.1 hot50.net
127.0.0.1 hotdatinglist.com 127.0.0.1 hotdatinglist.com
@ -30366,6 +30387,7 @@
127.0.0.1 housead.umeng.com 127.0.0.1 housead.umeng.com
127.0.0.1 howaboutwe.go2cloud.org 127.0.0.1 howaboutwe.go2cloud.org
127.0.0.1 howardchui.us.intellitxt.com 127.0.0.1 howardchui.us.intellitxt.com
127.0.0.1 howrowthettof.com
127.0.0.1 hoz01.voluumtrk.com 127.0.0.1 hoz01.voluumtrk.com
127.0.0.1 hpglobal.112.2o7.net 127.0.0.1 hpglobal.112.2o7.net
127.0.0.1 hphqglobal.112.2o7.net 127.0.0.1 hphqglobal.112.2o7.net
@ -30486,13 +30508,16 @@
127.0.0.1 i.n.jwpltx.com 127.0.0.1 i.n.jwpltx.com
127.0.0.1 i.nuseek.com 127.0.0.1 i.nuseek.com
127.0.0.1 i.offerx.co.uk 127.0.0.1 i.offerx.co.uk
127.0.0.1 i.playnow.guru
127.0.0.1 i.plug.it 127.0.0.1 i.plug.it
127.0.0.1 i.po.st 127.0.0.1 i.po.st
127.0.0.1 i.psa-ads.openx.com 127.0.0.1 i.psa-ads.openx.com
127.0.0.1 i.r1-cdn.net 127.0.0.1 i.r1-cdn.net
127.0.0.1 i.sahcdn.com 127.0.0.1 i.sahcdn.com
127.0.0.1 i.sbitinbsjs.info 127.0.0.1 i.sbitinbsjs.info
127.0.0.1 i.serves.live
127.0.0.1 i.simpli.fi 127.0.0.1 i.simpli.fi
127.0.0.1 i.socdm.com
127.0.0.1 i.tapit.com 127.0.0.1 i.tapit.com
127.0.0.1 i.tfag.de 127.0.0.1 i.tfag.de
127.0.0.1 i.theuseful.com 127.0.0.1 i.theuseful.com
@ -30659,7 +30684,6 @@
127.0.0.1 iframe.mediaplazza.com 127.0.0.1 iframe.mediaplazza.com
127.0.0.1 iframe.sponsorpay.com 127.0.0.1 iframe.sponsorpay.com
127.0.0.1 iframe.travel.yahoo.com 127.0.0.1 iframe.travel.yahoo.com
127.0.0.1 iframehost.com
127.0.0.1 iframes.hustler.com 127.0.0.1 iframes.hustler.com
127.0.0.1 iframesrccdn1.adexprt.com 127.0.0.1 iframesrccdn1.adexprt.com
127.0.0.1 iframesrccdn2.adexprt.com 127.0.0.1 iframesrccdn2.adexprt.com
@ -30846,7 +30870,6 @@
127.0.0.1 imaginemedia.net 127.0.0.1 imaginemedia.net
127.0.0.1 imap.linkshare.com 127.0.0.1 imap.linkshare.com
127.0.0.1 imatmobile.com 127.0.0.1 imatmobile.com
127.0.0.1 imb.mobitkr.com
127.0.0.1 imedia.co.il 127.0.0.1 imedia.co.il
127.0.0.1 imeds.ero-advertising.com 127.0.0.1 imeds.ero-advertising.com
127.0.0.1 imeseonetwork.go2cloud.org 127.0.0.1 imeseonetwork.go2cloud.org
@ -30945,6 +30968,7 @@
127.0.0.1 img04.webtrekk.net 127.0.0.1 img04.webtrekk.net
127.0.0.1 img05.webtrekk.net 127.0.0.1 img05.webtrekk.net
127.0.0.1 img06.webtrekk.net 127.0.0.1 img06.webtrekk.net
127.0.0.1 img1.nend.net
127.0.0.1 img1.webring.com 127.0.0.1 img1.webring.com
127.0.0.1 img2.freeze.com 127.0.0.1 img2.freeze.com
127.0.0.1 img2.paipaiimg.com 127.0.0.1 img2.paipaiimg.com
@ -31314,7 +31338,6 @@
127.0.0.1 j.adlooxtracking.com 127.0.0.1 j.adlooxtracking.com
127.0.0.1 j.clickdensity.com 127.0.0.1 j.clickdensity.com
127.0.0.1 j.kissinsights.com 127.0.0.1 j.kissinsights.com
127.0.0.1 j.mp
127.0.0.1 j.ophan.co.uk 127.0.0.1 j.ophan.co.uk
127.0.0.1 j.rvttrack.com 127.0.0.1 j.rvttrack.com
127.0.0.1 j.sahcdn.com 127.0.0.1 j.sahcdn.com
@ -33074,6 +33097,7 @@
127.0.0.1 mediauk.247realmedia.com 127.0.0.1 mediauk.247realmedia.com
127.0.0.1 mediavisor.doubleclick.com 127.0.0.1 mediavisor.doubleclick.com
127.0.0.1 mediavisor.doubleclick.net 127.0.0.1 mediavisor.doubleclick.net
127.0.0.1 mediawhirl.net
127.0.0.1 mediciinternet.go2cloud.org 127.0.0.1 mediciinternet.go2cloud.org
127.0.0.1 medicinenet.us.intellitxt.com 127.0.0.1 medicinenet.us.intellitxt.com
127.0.0.1 medio.com 127.0.0.1 medio.com
@ -33159,7 +33183,6 @@
127.0.0.1 metrics.loomia.com 127.0.0.1 metrics.loomia.com
127.0.0.1 metrics.mcafee.com 127.0.0.1 metrics.mcafee.com
127.0.0.1 metrics.mmailhost.com 127.0.0.1 metrics.mmailhost.com
127.0.0.1 metrics.mozilla.com
127.0.0.1 metrics.nexgen.neustar.biz 127.0.0.1 metrics.nexgen.neustar.biz
127.0.0.1 metrics.payback.de 127.0.0.1 metrics.payback.de
127.0.0.1 metrics.premiereradio.net 127.0.0.1 metrics.premiereradio.net
@ -33609,7 +33632,6 @@
127.0.0.1 my.applifier.com 127.0.0.1 my.applifier.com
127.0.0.1 my.blueadvertise.com 127.0.0.1 my.blueadvertise.com
127.0.0.1 my.hellobar.com 127.0.0.1 my.hellobar.com
127.0.0.1 my.leadpages.net
127.0.0.1 my.media-servers.net 127.0.0.1 my.media-servers.net
127.0.0.1 my.mobfox.com 127.0.0.1 my.mobfox.com
127.0.0.1 my.omniture.com 127.0.0.1 my.omniture.com
@ -33716,6 +33738,7 @@
127.0.0.1 n-tv.de.intellitxt.com 127.0.0.1 n-tv.de.intellitxt.com
127.0.0.1 n-tv.met.vgwort.de 127.0.0.1 n-tv.met.vgwort.de
127.0.0.1 n.bodybuilding.com 127.0.0.1 n.bodybuilding.com
127.0.0.1 n.fclick-adnow.com
127.0.0.1 n.gemini.yahoo.com 127.0.0.1 n.gemini.yahoo.com
127.0.0.1 n.ladycash.ru 127.0.0.1 n.ladycash.ru
127.0.0.1 n.lcads.ru 127.0.0.1 n.lcads.ru
@ -33782,6 +33805,7 @@
127.0.0.1 nancy-mosca.us 127.0.0.1 nancy-mosca.us
127.0.0.1 nandp.go2cloud.org 127.0.0.1 nandp.go2cloud.org
127.0.0.1 nanigans.com 127.0.0.1 nanigans.com
127.0.0.1 nanoadexchange.com
127.0.0.1 nanostats.nanopress.it 127.0.0.1 nanostats.nanopress.it
127.0.0.1 naomi-thorn.us 127.0.0.1 naomi-thorn.us
127.0.0.1 napster.searchwho.com 127.0.0.1 napster.searchwho.com
@ -34006,6 +34030,7 @@
127.0.0.1 nl1.ero-advertising.com 127.0.0.1 nl1.ero-advertising.com
127.0.0.1 nl2.ero-advertising.com 127.0.0.1 nl2.ero-advertising.com
127.0.0.1 nlbanner.nl 127.0.0.1 nlbanner.nl
127.0.0.1 nlett3r.com
127.0.0.1 nlhra.voluumtrk.com 127.0.0.1 nlhra.voluumtrk.com
127.0.0.1 nm.netmng.com 127.0.0.1 nm.netmng.com
127.0.0.1 nmaio.voluumtrk.com 127.0.0.1 nmaio.voluumtrk.com
@ -34144,6 +34169,7 @@
127.0.0.1 nuera.go2cloud.org 127.0.0.1 nuera.go2cloud.org
127.0.0.1 nuggad.net 127.0.0.1 nuggad.net
127.0.0.1 numb.hotshare.biz 127.0.0.1 numb.hotshare.biz
127.0.0.1 nurno.com
127.0.0.1 nv.ad.naver.com 127.0.0.1 nv.ad.naver.com
127.0.0.1 nv4n8.voluumtrk.com 127.0.0.1 nv4n8.voluumtrk.com
127.0.0.1 nvk.realsecuredredirect.com 127.0.0.1 nvk.realsecuredredirect.com
@ -34196,6 +34222,7 @@
127.0.0.1 o.addthis.com 127.0.0.1 o.addthis.com
127.0.0.1 o.leadbolt.com 127.0.0.1 o.leadbolt.com
127.0.0.1 o.sa.aol.com 127.0.0.1 o.sa.aol.com
127.0.0.1 o.ss2.us
127.0.0.1 o.swisscom.ch 127.0.0.1 o.swisscom.ch
127.0.0.1 o.xbox.com 127.0.0.1 o.xbox.com
127.0.0.1 o.yieldsquare.com 127.0.0.1 o.yieldsquare.com
@ -34443,6 +34470,7 @@
127.0.0.1 onclickads.net 127.0.0.1 onclickads.net
127.0.0.1 onclickads2.net 127.0.0.1 onclickads2.net
127.0.0.1 onclickrev.com 127.0.0.1 onclickrev.com
127.0.0.1 onclkds.com
127.0.0.1 one-time-offer.com 127.0.0.1 one-time-offer.com
127.0.0.1 one.123counters.com 127.0.0.1 one.123counters.com
127.0.0.1 oneandonlynetwork.com 127.0.0.1 oneandonlynetwork.com
@ -34625,6 +34653,7 @@
127.0.0.1 outils.acf-webmaster.net 127.0.0.1 outils.acf-webmaster.net
127.0.0.1 outils.f5biz.com 127.0.0.1 outils.f5biz.com
127.0.0.1 outpost.real.com 127.0.0.1 outpost.real.com
127.0.0.1 output.nend.net
127.0.0.1 outsidethebeltway.us.intellitxt.com 127.0.0.1 outsidethebeltway.us.intellitxt.com
127.0.0.1 ov.yahoo.co.jp 127.0.0.1 ov.yahoo.co.jp
127.0.0.1 overpro.com 127.0.0.1 overpro.com
@ -34842,6 +34871,7 @@
127.0.0.1 papayamobile.com 127.0.0.1 papayamobile.com
127.0.0.1 papi.slideme.org 127.0.0.1 papi.slideme.org
127.0.0.1 paramount.go2cloud.org 127.0.0.1 paramount.go2cloud.org
127.0.0.1 pardtosinsing.ru
127.0.0.1 parenting.searchwho.com 127.0.0.1 parenting.searchwho.com
127.0.0.1 paris.typepad.com 127.0.0.1 paris.typepad.com
127.0.0.1 paritycube.go2cloud.org 127.0.0.1 paritycube.go2cloud.org
@ -35247,6 +35277,7 @@
127.0.0.1 player-services.goviral-content.com 127.0.0.1 player-services.goviral-content.com
127.0.0.1 player.piksel.com 127.0.0.1 player.piksel.com
127.0.0.1 playminigolf.com 127.0.0.1 playminigolf.com
127.0.0.1 playnow.guru
127.0.0.1 playtomic.com 127.0.0.1 playtomic.com
127.0.0.1 plb27.voluumtrk.com 127.0.0.1 plb27.voluumtrk.com
127.0.0.1 pleasedontslaymy.download 127.0.0.1 pleasedontslaymy.download
@ -35427,6 +35458,7 @@
127.0.0.1 preciselylocate.com 127.0.0.1 preciselylocate.com
127.0.0.1 precisionleads.go2cloud.org 127.0.0.1 precisionleads.go2cloud.org
127.0.0.1 predictad.com 127.0.0.1 predictad.com
127.0.0.1 predictivadvertising.com
127.0.0.1 predictivenetworks.com 127.0.0.1 predictivenetworks.com
127.0.0.1 preferences.truste.com 127.0.0.1 preferences.truste.com
127.0.0.1 preferredpublishers.go2cloud.org 127.0.0.1 preferredpublishers.go2cloud.org
@ -35907,6 +35939,7 @@
127.0.0.1 r.369dl.com 127.0.0.1 r.369dl.com
127.0.0.1 r.ads.zynga.com 127.0.0.1 r.ads.zynga.com
127.0.0.1 r.adserver01.de 127.0.0.1 r.adserver01.de
127.0.0.1 r.af-share.jp
127.0.0.1 r.aol.com 127.0.0.1 r.aol.com
127.0.0.1 r.apina.biz 127.0.0.1 r.apina.biz
127.0.0.1 r.bbci.co.uk 127.0.0.1 r.bbci.co.uk
@ -35942,7 +35975,6 @@
127.0.0.1 r.zeroredirect1.com 127.0.0.1 r.zeroredirect1.com
127.0.0.1 r.zeroredirect2.com 127.0.0.1 r.zeroredirect2.com
127.0.0.1 r0d2x.voluumtrk.com 127.0.0.1 r0d2x.voluumtrk.com
127.0.0.1 r1---sn-vgqsen7z.googlevideo.com
127.0.0.1 r1.ace.advertising.com 127.0.0.1 r1.ace.advertising.com
127.0.0.1 r1.beta.ace.advertising.com 127.0.0.1 r1.beta.ace.advertising.com
127.0.0.1 r1.computerbild.de 127.0.0.1 r1.computerbild.de
@ -35950,27 +35982,20 @@
127.0.0.1 r1.fmpub.net 127.0.0.1 r1.fmpub.net
127.0.0.1 r1.pcwelt.de 127.0.0.1 r1.pcwelt.de
127.0.0.1 r1.plugrush.com 127.0.0.1 r1.plugrush.com
127.0.0.1 r1.sn-vgqsen7z.googlevideo.com
127.0.0.1 r1.zedo.com 127.0.0.1 r1.zedo.com
127.0.0.1 r10.cooleremail.com 127.0.0.1 r10.cooleremail.com
127.0.0.1 r11.cooleremail.com 127.0.0.1 r11.cooleremail.com
127.0.0.1 r17---sn-vgqsenes.googlevideo.com
127.0.0.1 r2---sn-vgqs7n7k.googlevideo.com
127.0.0.1 r2.cooleremail.com 127.0.0.1 r2.cooleremail.com
127.0.0.1 r2.linksynergy.com 127.0.0.1 r2.linksynergy.com
127.0.0.1 r2.plugrush.com 127.0.0.1 r2.plugrush.com
127.0.0.1 r20---sn-vgqs7ne7.googlevideo.com
127.0.0.1 r20.rs6.net 127.0.0.1 r20.rs6.net
127.0.0.1 r20.sn-vgqs7ne7.googlevideo.com
127.0.0.1 r2fjs.voluumtrk.com 127.0.0.1 r2fjs.voluumtrk.com
127.0.0.1 r2jmarketing.go2cloud.org 127.0.0.1 r2jmarketing.go2cloud.org
127.0.0.1 r2v3n.voluumtrk.com 127.0.0.1 r2v3n.voluumtrk.com
127.0.0.1 r3.cooleremail.com 127.0.0.1 r3.cooleremail.com
127.0.0.1 r3.plugrush.com 127.0.0.1 r3.plugrush.com
127.0.0.1 r4---sn-vgqs7nez.googlevideo.com
127.0.0.1 r4.cooleremail.com 127.0.0.1 r4.cooleremail.com
127.0.0.1 r4.plugrush.com 127.0.0.1 r4.plugrush.com
127.0.0.1 r4.sn-vgqs7nez.googlevideo.com
127.0.0.1 r4zih.voluumtrk.com 127.0.0.1 r4zih.voluumtrk.com
127.0.0.1 r5.cooleremail.com 127.0.0.1 r5.cooleremail.com
127.0.0.1 r5.plugrush.com 127.0.0.1 r5.plugrush.com
@ -36032,6 +36057,7 @@
127.0.0.1 rates.insureship.com 127.0.0.1 rates.insureship.com
127.0.0.1 rating.openstat.com 127.0.0.1 rating.openstat.com
127.0.0.1 rating.openstat.ru 127.0.0.1 rating.openstat.ru
127.0.0.1 ratsparropret.ru
127.0.0.1 ratx9.voluumtrk.com 127.0.0.1 ratx9.voluumtrk.com
127.0.0.1 ravenstonedigital.go2cloud.org 127.0.0.1 ravenstonedigital.go2cloud.org
127.0.0.1 rawdinner.justclick.ru 127.0.0.1 rawdinner.justclick.ru
@ -36153,6 +36179,7 @@
127.0.0.1 redirects.timesink.com 127.0.0.1 redirects.timesink.com
127.0.0.1 reditions.net 127.0.0.1 reditions.net
127.0.0.1 redmas.com 127.0.0.1 redmas.com
127.0.0.1 redrct.site
127.0.0.1 redshiftleads.steele.net 127.0.0.1 redshiftleads.steele.net
127.0.0.1 redtube.yoshatia.com 127.0.0.1 redtube.yoshatia.com
127.0.0.1 reduxmedia.com 127.0.0.1 reduxmedia.com
@ -36395,12 +36422,12 @@
127.0.0.1 roia.hutchmedia.com 127.0.0.1 roia.hutchmedia.com
127.0.0.1 roiadtracker.com 127.0.0.1 roiadtracker.com
127.0.0.1 roitracker.com 127.0.0.1 roitracker.com
127.0.0.1 rollbar.com
127.0.0.1 romanticfm.2cnt.net 127.0.0.1 romanticfm.2cnt.net
127.0.0.1 romview.atdmt.com.915.9004.302br.net 127.0.0.1 romview.atdmt.com.915.9004.302br.net
127.0.0.1 ronaldheft.com 127.0.0.1 ronaldheft.com
127.0.0.1 roomkey.d1.sc.omtrdc.net 127.0.0.1 roomkey.d1.sc.omtrdc.net
127.0.0.1 rootzwiki.us.intellitxt.com 127.0.0.1 rootzwiki.us.intellitxt.com
127.0.0.1 ropretratspar.com
127.0.0.1 rose.ixbt.com 127.0.0.1 rose.ixbt.com
127.0.0.1 rosettastone.tt.omtrdc.net 127.0.0.1 rosettastone.tt.omtrdc.net
127.0.0.1 rotabanner.kulichki.net 127.0.0.1 rotabanner.kulichki.net
@ -36529,11 +36556,13 @@
127.0.0.1 ryerose.net 127.0.0.1 ryerose.net
127.0.0.1 rygpq.voluumtrk.com 127.0.0.1 rygpq.voluumtrk.com
127.0.0.1 ryield.jmp9.com 127.0.0.1 ryield.jmp9.com
127.0.0.1 ryropthetsed.ru
127.0.0.1 rytj3.voluumtrk.com 127.0.0.1 rytj3.voluumtrk.com
127.0.0.1 rzr.tractionize.com 127.0.0.1 rzr.tractionize.com
127.0.0.1 s-adserver.cxad.cxense.com 127.0.0.1 s-adserver.cxad.cxense.com
127.0.0.1 s-adserver.sandbox.cxad.cxense.com 127.0.0.1 s-adserver.sandbox.cxad.cxense.com
127.0.0.1 s-assets.tp-cdn.com 127.0.0.1 s-assets.tp-cdn.com
127.0.0.1 s-cs.send.microad.jp
127.0.0.1 s-jsonp.moatads.com 127.0.0.1 s-jsonp.moatads.com
127.0.0.1 s-yoolk-banner-assets.yoolk.com 127.0.0.1 s-yoolk-banner-assets.yoolk.com
127.0.0.1 s-yoolk-billboard-assets.yoolk.com 127.0.0.1 s-yoolk-billboard-assets.yoolk.com
@ -36748,6 +36777,7 @@
127.0.0.1 s90.cnzz.com 127.0.0.1 s90.cnzz.com
127.0.0.1 s95.research.de.com 127.0.0.1 s95.research.de.com
127.0.0.1 s9kpd.voluumtrk.com 127.0.0.1 s9kpd.voluumtrk.com
127.0.0.1 sa-receiver.sematext.com
127.0.0.1 sa.bbc.co.uk 127.0.0.1 sa.bbc.co.uk
127.0.0.1 sa.bbc.com 127.0.0.1 sa.bbc.com
127.0.0.1 sa.seotoaster.com 127.0.0.1 sa.seotoaster.com
@ -36845,7 +36875,6 @@
127.0.0.1 script.ioam.de 127.0.0.1 script.ioam.de
127.0.0.1 script.leadboxer.com 127.0.0.1 script.leadboxer.com
127.0.0.1 script.opentracker.net 127.0.0.1 script.opentracker.net
127.0.0.1 script.starpass.fr
127.0.0.1 script.tailsweep.com 127.0.0.1 script.tailsweep.com
127.0.0.1 scripts.adrcdn.com 127.0.0.1 scripts.adrcdn.com
127.0.0.1 scripts.affiliatefuture.com 127.0.0.1 scripts.affiliatefuture.com
@ -37042,7 +37071,6 @@
127.0.0.1 selina-wimmer.us 127.0.0.1 selina-wimmer.us
127.0.0.1 sellads.eu 127.0.0.1 sellads.eu
127.0.0.1 sem.shopexplorer.com 127.0.0.1 sem.shopexplorer.com
127.0.0.1 sematext.com
127.0.0.1 semrush.com 127.0.0.1 semrush.com
127.0.0.1 send.microad.jp 127.0.0.1 send.microad.jp
127.0.0.1 send4fun.com 127.0.0.1 send4fun.com
@ -37513,8 +37541,6 @@
127.0.0.1 sndkorea.co.kr 127.0.0.1 sndkorea.co.kr
127.0.0.1 sndkorea.nowcdn.co.kr 127.0.0.1 sndkorea.nowcdn.co.kr
127.0.0.1 snimi-sam.justclick.ru 127.0.0.1 snimi-sam.justclick.ru
127.0.0.1 snippets-stats.mozilla.org
127.0.0.1 snippets.mozilla.com
127.0.0.1 snonline.ivwbox.de 127.0.0.1 snonline.ivwbox.de
127.0.0.1 snowplow-collector.sugarops.com 127.0.0.1 snowplow-collector.sugarops.com
127.0.0.1 soa.adition.com 127.0.0.1 soa.adition.com
@ -38244,6 +38270,7 @@
127.0.0.1 stats.united-domains.de 127.0.0.1 stats.united-domains.de
127.0.0.1 stats.unity3d.com 127.0.0.1 stats.unity3d.com
127.0.0.1 stats.uswitch.com 127.0.0.1 stats.uswitch.com
127.0.0.1 stats.valaffiliates.com
127.0.0.1 stats.vertriebsassistent.de 127.0.0.1 stats.vertriebsassistent.de
127.0.0.1 stats.viddler.com 127.0.0.1 stats.viddler.com
127.0.0.1 stats.virtuemart.net 127.0.0.1 stats.virtuemart.net
@ -38491,6 +38518,7 @@
127.0.0.1 sync.livejasmin.com 127.0.0.1 sync.livejasmin.com
127.0.0.1 sync.mathtag.com 127.0.0.1 sync.mathtag.com
127.0.0.1 sync.smarttds.ru 127.0.0.1 sync.smarttds.ru
127.0.0.1 sync.teads.tv
127.0.0.1 sync.zenoviaexchange.com 127.0.0.1 sync.zenoviaexchange.com
127.0.0.1 syndicate.payloadz.com 127.0.0.1 syndicate.payloadz.com
127.0.0.1 syndicate.powerofads.com 127.0.0.1 syndicate.powerofads.com
@ -38675,7 +38703,6 @@
127.0.0.1 tag.gamecentral.hiro.tv 127.0.0.1 tag.gamecentral.hiro.tv
127.0.0.1 tag.gstat.orangeportails.net 127.0.0.1 tag.gstat.orangeportails.net
127.0.0.1 tag.myplay.com 127.0.0.1 tag.myplay.com
127.0.0.1 tag.navdmp.com
127.0.0.1 tag.reachadv.it 127.0.0.1 tag.reachadv.it
127.0.0.1 tag.researchnow.com 127.0.0.1 tag.researchnow.com
127.0.0.1 tag.shopping-feed.com 127.0.0.1 tag.shopping-feed.com
@ -38944,6 +38971,7 @@
127.0.0.1 tfn.das.tamedia.ch 127.0.0.1 tfn.das.tamedia.ch
127.0.0.1 tfncdn.thaflynation.com 127.0.0.1 tfncdn.thaflynation.com
127.0.0.1 tfp.2ref.co 127.0.0.1 tfp.2ref.co
127.0.0.1 tg.socdm.com
127.0.0.1 tga.acs86.com 127.0.0.1 tga.acs86.com
127.0.0.1 tga.csbew.com 127.0.0.1 tga.csbew.com
127.0.0.1 tgbvfr.website 127.0.0.1 tgbvfr.website
@ -38969,7 +38997,6 @@
127.0.0.1 thehitsusa.com 127.0.0.1 thehitsusa.com
127.0.0.1 theinterwebs.space 127.0.0.1 theinterwebs.space
127.0.0.1 theoads.com 127.0.0.1 theoads.com
127.0.0.1 theoads.com.
127.0.0.1 theodosium.com 127.0.0.1 theodosium.com
127.0.0.1 thepoint.go2cloud.org 127.0.0.1 thepoint.go2cloud.org
127.0.0.1 theresa-buchman.us 127.0.0.1 theresa-buchman.us
@ -39040,7 +39067,6 @@
127.0.0.1 tigerloads.com 127.0.0.1 tigerloads.com
127.0.0.1 tigertext.com 127.0.0.1 tigertext.com
127.0.0.1 til.go2cloud.org 127.0.0.1 til.go2cloud.org
127.0.0.1 tiles.services.mozilla.com
127.0.0.1 tiller.co 127.0.0.1 tiller.co
127.0.0.1 tim-eckhoff.us 127.0.0.1 tim-eckhoff.us
127.0.0.1 timebus2.112.2o7.net 127.0.0.1 timebus2.112.2o7.net
@ -39203,6 +39229,7 @@
127.0.0.1 tr.ilius.net 127.0.0.1 tr.ilius.net
127.0.0.1 tr.interlake.net 127.0.0.1 tr.interlake.net
127.0.0.1 tr.newsletter.capdecision.fr 127.0.0.1 tr.newsletter.capdecision.fr
127.0.0.1 tr.note001.com
127.0.0.1 tr.sjc.contextweb.com 127.0.0.1 tr.sjc.contextweb.com
127.0.0.1 tr.x-tk.net 127.0.0.1 tr.x-tk.net
127.0.0.1 tr1.myroitracking.com 127.0.0.1 tr1.myroitracking.com
@ -39353,6 +39380,7 @@
127.0.0.1 track.reinvigorate.net 127.0.0.1 track.reinvigorate.net
127.0.0.1 track.right-ads.com 127.0.0.1 track.right-ads.com
127.0.0.1 track.ringcentral.com 127.0.0.1 track.ringcentral.com
127.0.0.1 track.scanguard.com
127.0.0.1 track.scanmyphones.com 127.0.0.1 track.scanmyphones.com
127.0.0.1 track.scorpiointeractive.com 127.0.0.1 track.scorpiointeractive.com
127.0.0.1 track.searchignite.com 127.0.0.1 track.searchignite.com
@ -39478,6 +39506,7 @@
127.0.0.1 trackicollect.ibase.fr 127.0.0.1 trackicollect.ibase.fr
127.0.0.1 tracking.1moretoy.com 127.0.0.1 tracking.1moretoy.com
127.0.0.1 tracking.247search.com 127.0.0.1 tracking.247search.com
127.0.0.1 tracking.4v4jfe79erfxfu8z8.com
127.0.0.1 tracking.actionads.ru 127.0.0.1 tracking.actionads.ru
127.0.0.1 tracking.adjug.com 127.0.0.1 tracking.adjug.com
127.0.0.1 tracking.admarketplace.net 127.0.0.1 tracking.admarketplace.net
@ -39582,6 +39611,8 @@
127.0.0.1 tracking.netzathleten-media.de 127.0.0.1 tracking.netzathleten-media.de
127.0.0.1 tracking.nuasti.com 127.0.0.1 tracking.nuasti.com
127.0.0.1 tracking.olx-st.com 127.0.0.1 tracking.olx-st.com
127.0.0.1 tracking.orixa-media.com
127.0.0.1 tracking.perfecttoolmedia.com
127.0.0.1 tracking.performancerevenues.com 127.0.0.1 tracking.performancerevenues.com
127.0.0.1 tracking.plattformad.com 127.0.0.1 tracking.plattformad.com
127.0.0.1 tracking.practicefusion.com 127.0.0.1 tracking.practicefusion.com
@ -39808,6 +39839,7 @@
127.0.0.1 trk.kissmetrics.com 127.0.0.1 trk.kissmetrics.com
127.0.0.1 trk.m.libero.it 127.0.0.1 trk.m.libero.it
127.0.0.1 trk.mailtoward.com 127.0.0.1 trk.mailtoward.com
127.0.0.1 trk.meacashtrk.com
127.0.0.1 trk.mobile-setting.com 127.0.0.1 trk.mobile-setting.com
127.0.0.1 trk.pswec.com 127.0.0.1 trk.pswec.com
127.0.0.1 trk.rrcpm.com 127.0.0.1 trk.rrcpm.com
@ -39817,6 +39849,7 @@
127.0.0.1 trk.vidible.tv 127.0.0.1 trk.vidible.tv
127.0.0.1 trk.vindicosuite.com 127.0.0.1 trk.vindicosuite.com
127.0.0.1 trk02.gtrk.info 127.0.0.1 trk02.gtrk.info
127.0.0.1 trk1.opanw.com
127.0.0.1 trk2it4.com 127.0.0.1 trk2it4.com
127.0.0.1 trk4.com 127.0.0.1 trk4.com
127.0.0.1 trkag1.com 127.0.0.1 trkag1.com
@ -40270,6 +40303,7 @@
127.0.0.1 uk.nedstatpro.net 127.0.0.1 uk.nedstatpro.net
127.0.0.1 uk.sitestat.com 127.0.0.1 uk.sitestat.com
127.0.0.1 uk.static.planet49.com 127.0.0.1 uk.static.planet49.com
127.0.0.1 uk.websearchnow.net
127.0.0.1 uk1.siteimprove.com 127.0.0.1 uk1.siteimprove.com
127.0.0.1 ukaffiliates2.com 127.0.0.1 ukaffiliates2.com
127.0.0.1 ukbanners.com 127.0.0.1 ukbanners.com
@ -40343,6 +40377,7 @@
127.0.0.1 urlcash.net 127.0.0.1 urlcash.net
127.0.0.1 urlcheck.hulu.com 127.0.0.1 urlcheck.hulu.com
127.0.0.1 urlstats.com 127.0.0.1 urlstats.com
127.0.0.1 urlzzz.com
127.0.0.1 urx.io 127.0.0.1 urx.io
127.0.0.1 us-ads.openx.net 127.0.0.1 us-ads.openx.net
127.0.0.1 us-east-1.profile-api.ads.linkedin.com 127.0.0.1 us-east-1.profile-api.ads.linkedin.com
@ -40361,7 +40396,6 @@
127.0.0.1 us.js.yimg.com 127.0.0.1 us.js.yimg.com
127.0.0.1 us.marketgid.com 127.0.0.1 us.marketgid.com
127.0.0.1 us.static.planet49.com 127.0.0.1 us.static.planet49.com
127.0.0.1 us.upgradenow24.com
127.0.0.1 us.wa.ui-portal.com 127.0.0.1 us.wa.ui-portal.com
127.0.0.1 us.webprodcdn.com 127.0.0.1 us.webprodcdn.com
127.0.0.1 us.winninganswers.net 127.0.0.1 us.winninganswers.net
@ -44182,6 +44216,7 @@
127.0.0.1 vt.adition.com 127.0.0.1 vt.adition.com
127.0.0.1 vtncmn.com 127.0.0.1 vtncmn.com
127.0.0.1 vtot.proxy.aol.com 127.0.0.1 vtot.proxy.aol.com
127.0.0.1 vtrack.larvtrk.com
127.0.0.1 vtracking.in.com 127.0.0.1 vtracking.in.com
127.0.0.1 vtrtl.de 127.0.0.1 vtrtl.de
127.0.0.1 vu.moatads.com 127.0.0.1 vu.moatads.com
@ -45057,10 +45092,13 @@
127.0.0.1 www.adclix.com 127.0.0.1 www.adclix.com
127.0.0.1 www.adclub.net 127.0.0.1 www.adclub.net
127.0.0.1 www.adcron.com 127.0.0.1 www.adcron.com
127.0.0.1 www.addiliate.com
127.0.0.1 www.addinto.com 127.0.0.1 www.addinto.com
127.0.0.1 www.addme.com 127.0.0.1 www.addme.com
127.0.0.1 www.addthis.com 127.0.0.1 www.addthis.com
127.0.0.1 www.adelina-ashman.us 127.0.0.1 www.adelina-ashman.us
127.0.0.1 www.adexc.net
127.0.0.1 www.adexce.net
127.0.0.1 www.adexcite.com 127.0.0.1 www.adexcite.com
127.0.0.1 www.adexm.com 127.0.0.1 www.adexm.com
127.0.0.1 www.adfest.com 127.0.0.1 www.adfest.com
@ -45094,6 +45132,7 @@
127.0.0.1 www.admarketplace.net 127.0.0.1 www.admarketplace.net
127.0.0.1 www.admarvel.com 127.0.0.1 www.admarvel.com
127.0.0.1 www.admaya.in 127.0.0.1 www.admaya.in
127.0.0.1 www.admaym.com
127.0.0.1 www.admedo.com 127.0.0.1 www.admedo.com
127.0.0.1 www.admeld.com 127.0.0.1 www.admeld.com
127.0.0.1 www.admestate.ru 127.0.0.1 www.admestate.ru
@ -45464,6 +45503,7 @@
127.0.0.1 www.cartonetwork.com 127.0.0.1 www.cartonetwork.com
127.0.0.1 www.cartoonnrtwork.com 127.0.0.1 www.cartoonnrtwork.com
127.0.0.1 www.casalemedia.com 127.0.0.1 www.casalemedia.com
127.0.0.1 www.casalparis.cat
127.0.0.1 www.cash-duck.com 127.0.0.1 www.cash-duck.com
127.0.0.1 www.cash4files.com 127.0.0.1 www.cash4files.com
127.0.0.1 www.cash4webmaster.de 127.0.0.1 www.cash4webmaster.de
@ -45499,6 +45539,7 @@
127.0.0.1 www.checkm8.com 127.0.0.1 www.checkm8.com
127.0.0.1 www.cherish-mauk.us 127.0.0.1 www.cherish-mauk.us
127.0.0.1 www.cheryl-edelstein.us 127.0.0.1 www.cheryl-edelstein.us
127.0.0.1 www.chokoladsrv.net
127.0.0.1 www.christi-canfield.us 127.0.0.1 www.christi-canfield.us
127.0.0.1 www.christie-hanlon.us 127.0.0.1 www.christie-hanlon.us
127.0.0.1 www.chrystal-hollins.us 127.0.0.1 www.chrystal-hollins.us
@ -45538,6 +45579,7 @@
127.0.0.1 www.clicksrvr.co 127.0.0.1 www.clicksrvr.co
127.0.0.1 www.clickstotrack.com 127.0.0.1 www.clickstotrack.com
127.0.0.1 www.clicksurecpa.com 127.0.0.1 www.clicksurecpa.com
127.0.0.1 www.clicksyndicatetracking.com
127.0.0.1 www.clickterra.net 127.0.0.1 www.clickterra.net
127.0.0.1 www.clicktracksolutions.com 127.0.0.1 www.clicktracksolutions.com
127.0.0.1 www.clicktraffix.com 127.0.0.1 www.clicktraffix.com
@ -45576,6 +45618,7 @@
127.0.0.1 www.compete.com 127.0.0.1 www.compete.com
127.0.0.1 www.competeinc.com 127.0.0.1 www.competeinc.com
127.0.0.1 www.comscore.com 127.0.0.1 www.comscore.com
127.0.0.1 www.comunedeicittadini.it
127.0.0.1 www.con.vmsn.de 127.0.0.1 www.con.vmsn.de
127.0.0.1 www.conductedresearch.com 127.0.0.1 www.conductedresearch.com
127.0.0.1 www.connectlinking1.com 127.0.0.1 www.connectlinking1.com
@ -45756,7 +45799,6 @@
127.0.0.1 www.doubleclick.ne.jp 127.0.0.1 www.doubleclick.ne.jp
127.0.0.1 www.doubleclick.net 127.0.0.1 www.doubleclick.net
127.0.0.1 www.doubleclick.net.my 127.0.0.1 www.doubleclick.net.my
127.0.0.1 www.download-performance.com
127.0.0.1 www.downloadcounter.de 127.0.0.1 www.downloadcounter.de
127.0.0.1 www.dprtb.com 127.0.0.1 www.dprtb.com
127.0.0.1 www.dragonballzhomeland.com 127.0.0.1 www.dragonballzhomeland.com
@ -46130,7 +46172,6 @@
127.0.0.1 www.ientry.com 127.0.0.1 www.ientry.com
127.0.0.1 www.ientrymail.com 127.0.0.1 www.ientrymail.com
127.0.0.1 www.ientrynetwork.net 127.0.0.1 www.ientrynetwork.net
127.0.0.1 www.iframehost.com
127.0.0.1 www.ifsmarketing.com 127.0.0.1 www.ifsmarketing.com
127.0.0.1 www.igain-mail.co.uk 127.0.0.1 www.igain-mail.co.uk
127.0.0.1 www.ignitad.com 127.0.0.1 www.ignitad.com
@ -46491,6 +46532,7 @@
127.0.0.1 www.mediamind.com 127.0.0.1 www.mediamind.com
127.0.0.1 www.mediaplex.com 127.0.0.1 www.mediaplex.com
127.0.0.1 www.mediasheva.com 127.0.0.1 www.mediasheva.com
127.0.0.1 www.mediawhirl.net
127.0.0.1 www.medio.com 127.0.0.1 www.medio.com
127.0.0.1 www.meethotties.mobi 127.0.0.1 www.meethotties.mobi
127.0.0.1 www.megacounter.de 127.0.0.1 www.megacounter.de
@ -46623,6 +46665,7 @@
127.0.0.1 www.n78adserv.com 127.0.0.1 www.n78adserv.com
127.0.0.1 www.n79adserv.com 127.0.0.1 www.n79adserv.com
127.0.0.1 www.nadia-carlyle.us 127.0.0.1 www.nadia-carlyle.us
127.0.0.1 www.nanoadexchange.com
127.0.0.1 www.naomi-thorn.us 127.0.0.1 www.naomi-thorn.us
127.0.0.1 www.naturalsearchtoolresults.com 127.0.0.1 www.naturalsearchtoolresults.com
127.0.0.1 www.navteq.com 127.0.0.1 www.navteq.com
@ -46647,11 +46690,13 @@
127.0.0.1 www.nicolette-brier.us 127.0.0.1 www.nicolette-brier.us
127.0.0.1 www.nicolette-salas.us 127.0.0.1 www.nicolette-salas.us
127.0.0.1 www.njashka.ru 127.0.0.1 www.njashka.ru
127.0.0.1 www.nlett3r.com
127.0.0.1 www.nngalleries2.com 127.0.0.1 www.nngalleries2.com
127.0.0.1 www.noelle-traxler.us 127.0.0.1 www.noelle-traxler.us
127.0.0.1 www.noelle-trotter.us 127.0.0.1 www.noelle-trotter.us
127.0.0.1 www.nonames.tk 127.0.0.1 www.nonames.tk
127.0.0.1 www.noowho.com 127.0.0.1 www.noowho.com
127.0.0.1 www.nurno.com
127.0.0.1 www.nvtrak.com 127.0.0.1 www.nvtrak.com
127.0.0.1 www.nxsrv1.com 127.0.0.1 www.nxsrv1.com
127.0.0.1 www.o2onbusiness.de 127.0.0.1 www.o2onbusiness.de
@ -46675,6 +46720,7 @@
127.0.0.1 www.omtrdc.net 127.0.0.1 www.omtrdc.net
127.0.0.1 www.onclickads.net 127.0.0.1 www.onclickads.net
127.0.0.1 www.onclickpredictiv.com 127.0.0.1 www.onclickpredictiv.com
127.0.0.1 www.onclkds.com
127.0.0.1 www.oneandonlynetwork.com 127.0.0.1 www.oneandonlynetwork.com
127.0.0.1 www.onelouder.com 127.0.0.1 www.onelouder.com
127.0.0.1 www.onestat.com 127.0.0.1 www.onestat.com
@ -46781,6 +46827,7 @@
127.0.0.1 www.pptrk.com 127.0.0.1 www.pptrk.com
127.0.0.1 www.prchecker.info 127.0.0.1 www.prchecker.info
127.0.0.1 www.preciselylocate.com 127.0.0.1 www.preciselylocate.com
127.0.0.1 www.predictivadvertising.com
127.0.0.1 www.premiumhdv.com 127.0.0.1 www.premiumhdv.com
127.0.0.1 www.primosearch.com 127.0.0.1 www.primosearch.com
127.0.0.1 www.pringotrack.com 127.0.0.1 www.pringotrack.com
@ -46883,7 +46930,6 @@
127.0.0.1 www.robotreplay.com 127.0.0.1 www.robotreplay.com
127.0.0.1 www.roiadtracker.com 127.0.0.1 www.roiadtracker.com
127.0.0.1 www.roitracker.com 127.0.0.1 www.roitracker.com
127.0.0.1 www.rollbar.com
127.0.0.1 www.rotrk.com 127.0.0.1 www.rotrk.com
127.0.0.1 www.roxyaffiliates.com 127.0.0.1 www.roxyaffiliates.com
127.0.0.1 www.rsmrttracking.com 127.0.0.1 www.rsmrttracking.com
@ -46960,7 +47006,6 @@
127.0.0.1 www.selina-krouse.us 127.0.0.1 www.selina-krouse.us
127.0.0.1 www.selipuquoe.com 127.0.0.1 www.selipuquoe.com
127.0.0.1 www.sellads.eu 127.0.0.1 www.sellads.eu
127.0.0.1 www.sematext.com
127.0.0.1 www.semrush.com 127.0.0.1 www.semrush.com
127.0.0.1 www.send4fun.com 127.0.0.1 www.send4fun.com
127.0.0.1 www.seniorttub.bid 127.0.0.1 www.seniorttub.bid
@ -47260,6 +47305,7 @@
127.0.0.1 www.upsellit.com 127.0.0.1 www.upsellit.com
127.0.0.1 www.upv4.moatads.com 127.0.0.1 www.upv4.moatads.com
127.0.0.1 www.urlstats.com 127.0.0.1 www.urlstats.com
127.0.0.1 www.urlzzz.com
127.0.0.1 www.urmediazone.com 127.0.0.1 www.urmediazone.com
127.0.0.1 www.urx.io 127.0.0.1 www.urx.io
127.0.0.1 www.usabilla.com 127.0.0.1 www.usabilla.com
@ -47275,6 +47321,7 @@
127.0.0.1 www.vayavicio.com 127.0.0.1 www.vayavicio.com
127.0.0.1 www.vdownloadall.com 127.0.0.1 www.vdownloadall.com
127.0.0.1 www.vectormarketing.com 127.0.0.1 www.vectormarketing.com
127.0.0.1 www.venturead.com
127.0.0.1 www.veruta.com 127.0.0.1 www.veruta.com
127.0.0.1 www.vibrantmedia.com 127.0.0.1 www.vibrantmedia.com
127.0.0.1 www.video-loader.com 127.0.0.1 www.video-loader.com
@ -47672,6 +47719,7 @@
127.0.0.1 xiazai.duba.net 127.0.0.1 xiazai.duba.net
127.0.0.1 xiti.com 127.0.0.1 xiti.com
127.0.0.1 xl.topstat.com 127.0.0.1 xl.topstat.com
127.0.0.1 xl415.com
127.0.0.1 xlite.counterpath.com 127.0.0.1 xlite.counterpath.com
127.0.0.1 xml.adtech.fr 127.0.0.1 xml.adtech.fr
127.0.0.1 xml.fusionxml.com 127.0.0.1 xml.fusionxml.com
@ -47873,6 +47921,7 @@
127.0.0.1 zbxproxy01.con.local.vmsn.de 127.0.0.1 zbxproxy01.con.local.vmsn.de
127.0.0.1 zc.zeroredirect1.com 127.0.0.1 zc.zeroredirect1.com
127.0.0.1 zc.zeroredirect2.com 127.0.0.1 zc.zeroredirect2.com
127.0.0.1 zc2.quebec-bin.com
127.0.0.1 zd.zeroredirect1.com 127.0.0.1 zd.zeroredirect1.com
127.0.0.1 zdau-builder.122.2o7.net 127.0.0.1 zdau-builder.122.2o7.net
127.0.0.1 zdau-zdnetau.122.2o7.net 127.0.0.1 zdau-zdnetau.122.2o7.net
@ -47974,4 +48023,4 @@
127.0.0.1 zz.zeroredirect1.com 127.0.0.1 zz.zeroredirect1.com
127.0.0.1 zzz.clickbank.net 127.0.0.1 zzz.clickbank.net
127.0.0.1 _thums.ero-advertising.com 127.0.0.1 _thums.ero-advertising.com
# Hosts: 47966 # Hosts: 48015

18
app/src/main/AndroidManifest.xml

@ -25,7 +25,7 @@
android:required="false"/> android:required="false"/>
<application <application
android:name=".app.BrowserApp" android:name=".BrowserApp"
android:allowBackup="true" android:allowBackup="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -38,9 +38,9 @@
android:value="2.1"/> android:value="2.1"/>
<activity <activity
android:name=".activity.MainActivity" android:name=".MainActivity"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|keyboard"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/Theme.LightTheme" android:theme="@style/Theme.LightTheme"
@ -122,8 +122,8 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.SettingsActivity" android:name=".settings.activity.SettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|keyboard"
android:label="@string/settings" android:label="@string/settings"
android:theme="@style/Theme.SettingsTheme"> android:theme="@style/Theme.SettingsTheme">
<intent-filter> <intent-filter>
@ -133,9 +133,9 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.IncognitoActivity" android:name=".IncognitoActivity"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|keyboard"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/Theme.DarkTheme" android:theme="@style/Theme.DarkTheme"
@ -147,8 +147,8 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.ReadingActivity" android:name=".reading.activity.ReadingActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|keyboard"
android:label="@string/reading_mode" android:label="@string/reading_mode"
android:theme="@style/Theme.SettingsTheme"> android:theme="@style/Theme.SettingsTheme">
<intent-filter> <intent-filter>

8
app/src/main/java/acr/browser/lightning/app/BrowserApp.java → app/src/main/java/acr/browser/lightning/BrowserApp.java

@ -1,4 +1,4 @@
package acr.browser.lightning.app; package acr.browser.lightning;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
@ -20,11 +20,13 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkExporter; import acr.browser.lightning.database.bookmark.BookmarkExporter;
import acr.browser.lightning.database.bookmark.legacy.LegacyBookmarkManager;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.database.bookmark.legacy.LegacyBookmarkManager;
import acr.browser.lightning.di.AppComponent;
import acr.browser.lightning.di.AppModule;
import acr.browser.lightning.di.DaggerAppComponent;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
import acr.browser.lightning.utils.MemoryLeakUtils; import acr.browser.lightning.utils.MemoryLeakUtils;

4
app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java → app/src/main/java/acr/browser/lightning/IncognitoActivity.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
@ -12,7 +12,7 @@ import com.anthonycr.bonsai.Completable;
import com.anthonycr.bonsai.CompletableAction; import com.anthonycr.bonsai.CompletableAction;
import com.anthonycr.bonsai.CompletableSubscriber; import com.anthonycr.bonsai.CompletableSubscriber;
import acr.browser.lightning.R; import acr.browser.lightning.browser.activity.BrowserActivity;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class IncognitoActivity extends BrowserActivity { public class IncognitoActivity extends BrowserActivity {

22
app/src/main/java/acr/browser/lightning/activity/MainActivity.java → app/src/main/java/acr/browser/lightning/MainActivity.java

@ -1,9 +1,10 @@
package acr.browser.lightning.activity; package acr.browser.lightning;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
@ -12,7 +13,7 @@ import com.anthonycr.bonsai.Completable;
import com.anthonycr.bonsai.CompletableAction; import com.anthonycr.bonsai.CompletableAction;
import com.anthonycr.bonsai.CompletableSubscriber; import com.anthonycr.bonsai.CompletableSubscriber;
import acr.browser.lightning.R; import acr.browser.lightning.browser.activity.BrowserActivity;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class MainActivity extends BrowserActivity { public class MainActivity extends BrowserActivity {
@ -76,5 +77,22 @@ public class MainActivity extends BrowserActivity {
}); });
} }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_P:
// Open a new private window
if(event.isShiftPressed()) {
startActivity(new Intent(this, IncognitoActivity.class));
overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale);
return true;
}
break;
}
}
return super.dispatchKeyEvent(event);
}
} }

4
app/src/main/java/acr/browser/lightning/activity/AppCompatPreferenceActivity.java

@ -40,11 +40,11 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
getDelegate().onPostCreate(savedInstanceState); getDelegate().onPostCreate(savedInstanceState);
} }
ActionBar getSupportActionBar() { protected final ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar(); return getDelegate().getSupportActionBar();
} }
void setSupportActionBar(@Nullable Toolbar toolbar) { protected final void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar); getDelegate().setSupportActionBar(toolbar);
} }

79
app/src/main/java/acr/browser/lightning/utils/AdBlock.java → app/src/main/java/acr/browser/lightning/adblock/AdBlock.java

@ -1,4 +1,4 @@
package acr.browser.lightning.utils; package acr.browser.lightning.adblock;
import android.app.Application; import android.app.Application;
import android.content.res.AssetManager; import android.content.res.AssetManager;
@ -16,7 +16,9 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
@ -24,6 +26,8 @@ import javax.inject.Singleton;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.StringBuilderUtils;
import acr.browser.lightning.utils.Utils;
@Singleton @Singleton
public class AdBlock { public class AdBlock {
@ -132,42 +136,16 @@ public class AdBlock {
String line; String line;
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
final List<String> domains = new ArrayList<>(1);
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
lineBuilder.append(line); lineBuilder.append(line);
if (!StringBuilderUtils.isEmpty(lineBuilder) && parseString(lineBuilder, domains);
!StringBuilderUtils.startsWith(lineBuilder, COMMENT)) {
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V4, EMPTY);
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V4_ALT, EMPTY);
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V6, EMPTY);
StringBuilderUtils.replace(lineBuilder, TAB, EMPTY);
int comment = lineBuilder.indexOf(COMMENT);
if (comment >= 0) {
lineBuilder.replace(comment, lineBuilder.length(), EMPTY);
}
StringBuilderUtils.trim(lineBuilder);
if (!StringBuilderUtils.isEmpty(lineBuilder) &&
!StringBuilderUtils.equals(lineBuilder, LOCALHOST)) {
while (StringBuilderUtils.contains(lineBuilder, SPACE)) {
int space = lineBuilder.indexOf(SPACE);
StringBuilder partial = StringBuilderUtils.substring(lineBuilder, 0, space);
StringBuilderUtils.trim(partial);
String partialLine = partial.toString();
mBlockedDomainsList.add(partialLine);
StringBuilderUtils.replace(lineBuilder, partialLine, EMPTY);
StringBuilderUtils.trim(lineBuilder);
}
if (lineBuilder.length() > 0) {
mBlockedDomainsList.add(lineBuilder.toString());
}
}
}
lineBuilder.setLength(0); lineBuilder.setLength(0);
} }
mBlockedDomainsList.addAll(domains);
Log.d(TAG, "Loaded ad list in: " + (System.currentTimeMillis() - time) + " ms"); Log.d(TAG, "Loaded ad list in: " + (System.currentTimeMillis() - time) + " ms");
} catch (IOException e) { } catch (IOException e) {
Log.wtf(TAG, "Reading blocked domains list from file '" Log.wtf(TAG, "Reading blocked domains list from file '"
@ -179,4 +157,41 @@ public class AdBlock {
}); });
} }
private static void parseString(@NonNull StringBuilder lineBuilder, @NonNull List<String> parsedList) {
if (!StringBuilderUtils.isEmpty(lineBuilder) &&
!StringBuilderUtils.startsWith(lineBuilder, COMMENT)) {
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V4, EMPTY);
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V4_ALT, EMPTY);
StringBuilderUtils.replace(lineBuilder, LOCAL_IP_V6, EMPTY);
StringBuilderUtils.replace(lineBuilder, TAB, EMPTY);
int comment = lineBuilder.indexOf(COMMENT);
if (comment >= 0) {
lineBuilder.replace(comment, lineBuilder.length(), EMPTY);
}
StringBuilderUtils.trim(lineBuilder);
if (!StringBuilderUtils.isEmpty(lineBuilder) &&
!StringBuilderUtils.equals(lineBuilder, LOCALHOST)) {
while (StringBuilderUtils.contains(lineBuilder, SPACE)) {
int space = lineBuilder.indexOf(SPACE);
StringBuilder partial = StringBuilderUtils.substring(lineBuilder, 0, space);
StringBuilderUtils.trim(partial);
String partialLine = partial.toString();
// Add string to list
parsedList.add(partialLine);
StringBuilderUtils.replace(lineBuilder, partialLine, EMPTY);
StringBuilderUtils.trim(lineBuilder);
}
if (lineBuilder.length() > 0) {
// Add string to list.
parsedList.add(lineBuilder.toString());
}
}
}
}
} }

29
app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java

@ -1,11 +1,13 @@
package acr.browser.lightning.browser; package acr.browser.lightning.browser;
import android.app.Activity; import android.app.Activity;
import android.app.Application;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.webkit.URLUtil;
import com.anthonycr.bonsai.CompletableOnSubscribe; import com.anthonycr.bonsai.CompletableOnSubscribe;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
@ -14,9 +16,10 @@ import javax.inject.Inject;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
@ -33,6 +36,7 @@ public class BrowserPresenter {
private static final String TAG = "BrowserPresenter"; private static final String TAG = "BrowserPresenter";
@NonNull private final TabsManager mTabsModel; @NonNull private final TabsManager mTabsModel;
@Inject Application mApplication;
@Inject PreferenceManager mPreferences; @Inject PreferenceManager mPreferences;
@NonNull private final BrowserView mView; @NonNull private final BrowserView mView;
@ -116,7 +120,7 @@ public class BrowserPresenter {
mView.updateProgress(newTab.getProgress()); mView.updateProgress(newTab.getProgress());
mView.setBackButtonEnabled(newTab.canGoBack()); mView.setBackButtonEnabled(newTab.canGoBack());
mView.setForwardButtonEnabled(newTab.canGoForward()); mView.setForwardButtonEnabled(newTab.canGoForward());
mView.updateUrl(newTab.getUrl(), true); mView.updateUrl(newTab.getUrl(), false);
mView.setTabView(newTab.getWebView()); mView.setTabView(newTab.getWebView());
int index = mTabsModel.indexOfTab(newTab); int index = mTabsModel.indexOfTab(newTab);
if (index >= 0) { if (index >= 0) {
@ -143,6 +147,19 @@ public class BrowserPresenter {
} }
@NonNull
private String mapHomepageToCurrentUrl() {
String homepage = mPreferences.getHomepage();
switch (homepage) {
case Constants.SCHEME_HOMEPAGE:
return Constants.FILE + StartPage.getStartPageFile(mApplication);
case Constants.SCHEME_BOOKMARKS:
return Constants.FILE + BookmarkPage.getBookmarkPage(mApplication, null);
default:
return homepage;
}
}
/** /**
* Deletes the tab at the specified position. * Deletes the tab at the specified position.
* *
@ -165,8 +182,8 @@ public class BrowserPresenter {
boolean shouldClose = mShouldClose && isShown && tabToDelete.isNewTab(); boolean shouldClose = mShouldClose && isShown && tabToDelete.isNewTab();
final LightningView currentTab = mTabsModel.getCurrentTab(); final LightningView currentTab = mTabsModel.getCurrentTab();
if (mTabsModel.size() == 1 && currentTab != null && if (mTabsModel.size() == 1 && currentTab != null &&
(UrlUtils.isStartPageUrl(currentTab.getUrl()) || URLUtil.isFileUrl(currentTab.getUrl()) &&
currentTab.getUrl().equals(mPreferences.getHomepage()))) { currentTab.getUrl().equals(mapHomepageToCurrentUrl())) {
mView.closeActivity(); mView.closeActivity();
return; return;
} else { } else {
@ -232,7 +249,7 @@ public class BrowserPresenter {
tab.loadUrl(url); tab.loadUrl(url);
} }
} else if (url != null) { } else if (url != null) {
if (url.startsWith(Constants.FILE)) { if (URLUtil.isFileUrl(url)) {
mView.showBlockedLocalFileDialog(new DialogInterface.OnClickListener() { mView.showBlockedLocalFileDialog(new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {

5
app/src/main/java/acr/browser/lightning/browser/BrowserView.java

@ -2,6 +2,7 @@ package acr.browser.lightning.browser;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.view.View; import android.view.View;
@ -11,7 +12,7 @@ public interface BrowserView {
void removeTabView(); void removeTabView();
void updateUrl(String url, boolean shortUrl); void updateUrl(@Nullable String url, boolean isLoading);
void updateProgress(int progress); void updateProgress(int progress);
@ -21,7 +22,7 @@ public interface BrowserView {
void closeActivity(); void closeActivity();
void showBlockedLocalFileDialog(DialogInterface.OnClickListener listener); void showBlockedLocalFileDialog(@NonNull DialogInterface.OnClickListener listener);
void showSnackbar(@StringRes int resource); void showSnackbar(@StringRes int resource);

71
app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.java

@ -0,0 +1,71 @@
package acr.browser.lightning.browser;
import android.app.Application;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import javax.inject.Inject;
import acr.browser.lightning.R;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.UrlUtils;
import acr.browser.lightning.utils.Utils;
/**
* A UI model for the search box.
*/
public class SearchBoxModel {
@Inject PreferenceManager mPreferences;
@Inject Application mApplication;
@NonNull private final String mUntitledTitle;
@Inject
public SearchBoxModel() {
BrowserApp.getAppComponent().inject(this);
mUntitledTitle = mApplication.getString(R.string.untitled);
}
/**
* Returns the contents of the search box based on a variety of factors.
* <ul>
* <li>The user's preference to show either the URL, domain, or page title</li>
* <li>Whether or not the current page is loading</li>
* <li>Whether or not the current page is a Lightning generated page.</li>
* </ul>
* This method uses the URL, title, and loading information to determine what
* should be displayed by the search box.
*
* @param url the URL of the current page.
* @param title the title of the current page, if known.
* @param isLoading whether the page is currently loading or not.
* @return the string that should be displayed by the search box.
*/
@NonNull
public String getDisplayContent(@NonNull String url, @Nullable String title, boolean isLoading) {
if (UrlUtils.isSpecialUrl(url)) {
return "";
} else if (isLoading) {
return url;
} else {
switch (mPreferences.getUrlBoxContentChoice()) {
default:
case 0: // Default, show only the domain
String domain = Utils.getDomainName(url);
return domain != null ? domain : url;
case 1: // URL, show the entire URL
return url;
case 2: // Title, show the page's title
if (!TextUtils.isEmpty(title)) {
return title;
} else {
return mUntitledTitle;
}
}
}
}
}

26
app/src/main/java/acr/browser/lightning/activity/TabsManager.java → app/src/main/java/acr/browser/lightning/browser/TabsManager.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.browser;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
@ -12,6 +12,7 @@ import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.webkit.URLUtil;
import android.webkit.WebView; import android.webkit.WebView;
import com.anthonycr.bonsai.Completable; import com.anthonycr.bonsai.Completable;
@ -30,9 +31,8 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.constant.DownloadsPage; import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.constant.StartPage;
@ -174,15 +174,15 @@ public class TabsManager {
}); });
} else if (UrlUtils.isDownloadsUrl(url)) { } else if (UrlUtils.isDownloadsUrl(url)) {
new DownloadsPage().getDownloadsPage() new DownloadsPage().getDownloadsPage()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<String>() { .subscribe(new SingleOnSubscribe<String>() {
@Override @Override
public void onItem(@Nullable String item) { public void onItem(@Nullable String item) {
Preconditions.checkNonNull(item); Preconditions.checkNonNull(item);
tab.loadUrl(item); tab.loadUrl(item);
} }
}); });
} else if (UrlUtils.isStartPageUrl(url)) { } else if (UrlUtils.isStartPageUrl(url)) {
new StartPage().getHomepage() new StartPage().getHomepage()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -214,7 +214,7 @@ public class TabsManager {
@Override @Override
public void onComplete() { public void onComplete() {
if (url != null) { if (url != null) {
if (url.startsWith(Constants.FILE)) { if (URLUtil.isFileUrl(url)) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
Dialog dialog = builder.setCancelable(true) Dialog dialog = builder.setCancelable(true)
.setTitle(R.string.title_warning) .setTitle(R.string.title_warning)

225
app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java → app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.java

@ -2,7 +2,7 @@
* Copyright 2015 Anthony Restaino * Copyright 2015 Anthony Restaino
*/ */
package acr.browser.lightning.activity; package acr.browser.lightning.browser.activity;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
@ -90,12 +90,15 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.reading.activity.ReadingActivity;
import acr.browser.lightning.browser.BookmarksView; import acr.browser.lightning.browser.BookmarksView;
import acr.browser.lightning.browser.BrowserPresenter; import acr.browser.lightning.browser.BrowserPresenter;
import acr.browser.lightning.browser.BrowserView; import acr.browser.lightning.browser.BrowserView;
import acr.browser.lightning.IncognitoActivity;
import acr.browser.lightning.browser.SearchBoxModel;
import acr.browser.lightning.browser.TabsManager;
import acr.browser.lightning.browser.TabsView; import acr.browser.lightning.browser.TabsView;
import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.constant.DownloadsPage; import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
@ -105,11 +108,14 @@ import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.browser.fragment.BookmarksFragment;
import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.browser.fragment.TabsFragment;
import acr.browser.lightning.interpolator.BezierDecelerateInterpolator; import acr.browser.lightning.interpolator.BezierDecelerateInterpolator;
import acr.browser.lightning.receiver.NetworkReceiver; import acr.browser.lightning.receiver.NetworkReceiver;
import acr.browser.lightning.search.SearchEngineProvider;
import acr.browser.lightning.search.SuggestionsAdapter; import acr.browser.lightning.search.SuggestionsAdapter;
import acr.browser.lightning.search.engine.BaseSearchEngine;
import acr.browser.lightning.settings.activity.SettingsActivity;
import acr.browser.lightning.utils.DrawableUtils; import acr.browser.lightning.utils.DrawableUtils;
import acr.browser.lightning.utils.IntentUtils; import acr.browser.lightning.utils.IntentUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
@ -185,8 +191,14 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// The singleton BookmarkManager // The singleton BookmarkManager
@Inject BookmarkModel mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject HistoryModel mHistoryModel;
@Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject LightningDialogBuilder mBookmarksDialogBuilder;
@Inject SearchBoxModel mSearchBoxModel;
@Inject SearchEngineProvider mSearchEngineProvider;
private TabsManager mTabsManager; private TabsManager mTabsManager;
// Image // Image
@ -216,7 +228,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
public abstract void updateHistory(@Nullable final String title, @NonNull final String url); public abstract void updateHistory(@Nullable final String title, @NonNull final String url);
@NonNull @NonNull
abstract Completable updateCookiePreference(); protected abstract Completable updateCookiePreference();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -327,8 +339,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
lp.height = LayoutParams.MATCH_PARENT; lp.height = LayoutParams.MATCH_PARENT;
customView.setLayoutParams(lp); customView.setLayoutParams(lp);
mArrowImage = (ImageView) customView.findViewById(R.id.arrow); mArrowImage = customView.findViewById(R.id.arrow);
FrameLayout arrowButton = (FrameLayout) customView.findViewById(R.id.arrow_button); FrameLayout arrowButton = customView.findViewById(R.id.arrow_button);
if (mShowTabsInDrawer) { if (mShowTabsInDrawer) {
if (mArrowImage.getWidth() <= 0) { if (mArrowImage.getWidth() <= 0) {
mArrowImage.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); mArrowImage.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
@ -366,7 +378,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
arrowButton.setOnClickListener(this); arrowButton.setOnClickListener(this);
// create the search EditText in the ToolBar // create the search EditText in the ToolBar
mSearch = (SearchView) customView.findViewById(R.id.search); mSearch = customView.findViewById(R.id.search);
mSearchBackground = customView.findViewById(R.id.search_container); mSearchBackground = customView.findViewById(R.id.search_container);
// initialize search background color // initialize search background color
@ -443,11 +455,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
* @return true if the panic trigger sent * @return true if the panic trigger sent
* the intent, false otherwise. * the intent, false otherwise.
*/ */
static boolean isPanicTrigger(@Nullable Intent intent) { protected static boolean isPanicTrigger(@Nullable Intent intent) {
return intent != null && INTENT_PANIC_TRIGGER.equals(intent.getAction()); return intent != null && INTENT_PANIC_TRIGGER.equals(intent.getAction());
} }
void panicClean() { protected void panicClean() {
Log.d(TAG, "Closing browser"); Log.d(TAG, "Closing browser");
mTabsManager.newTab(this, "", false); mTabsManager.newTab(this, "", false);
mTabsManager.switchToTab(0); mTabsManager.switchToTab(0);
@ -508,7 +520,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
final LightningView currentView = mTabsManager.getCurrentTab(); final LightningView currentView = mTabsManager.getCurrentTab();
if (!hasFocus && currentView != null) { if (!hasFocus && currentView != null) {
setIsLoading(currentView.getProgress() < 100); setIsLoading(currentView.getProgress() < 100);
updateUrl(currentView.getUrl(), true); updateUrl(currentView.getUrl(), false);
} else if (hasFocus && currentView != null) { } else if (hasFocus && currentView != null) {
// Hack to make sure the text gets selected // Hack to make sure the text gets selected
@ -652,45 +664,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
setFullscreen(mPreferences.getHideStatusBarEnabled(), false); setFullscreen(mPreferences.getHideStatusBarEnabled(), false);
switch (mPreferences.getSearchChoice()) { BaseSearchEngine currentSearchEngine = mSearchEngineProvider.getCurrentSearchEngine();
case 0: mSearchText = currentSearchEngine.getQueryUrl();
mSearchText = mPreferences.getSearchUrl();
if (!mSearchText.startsWith(Constants.HTTP)
&& !mSearchText.startsWith(Constants.HTTPS)) {
mSearchText = Constants.GOOGLE_SEARCH;
}
break;
case 1:
mSearchText = Constants.GOOGLE_SEARCH;
break;
case 2:
mSearchText = Constants.ASK_SEARCH;
break;
case 3:
mSearchText = Constants.BING_SEARCH;
break;
case 4:
mSearchText = Constants.YAHOO_SEARCH;
break;
case 5:
mSearchText = Constants.STARTPAGE_SEARCH;
break;
case 6:
mSearchText = Constants.STARTPAGE_MOBILE_SEARCH;
break;
case 7:
mSearchText = Constants.DUCK_SEARCH;
break;
case 8:
mSearchText = Constants.DUCK_LITE_SEARCH;
break;
case 9:
mSearchText = Constants.BAIDU_SEARCH;
break;
case 10:
mSearchText = Constants.YANDEX_SEARCH;
break;
}
updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe(); updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe();
mProxyUtils.updateProxySettings(this); mProxyUtils.updateProxySettings(this);
@ -749,52 +724,70 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
// Keyboard shortcuts // Keyboard shortcuts
if (event.isCtrlPressed() && event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) { if (event.isCtrlPressed()) {
case KeyEvent.KEYCODE_T: switch (event.getKeyCode()) {
// Open new tab case KeyEvent.KEYCODE_F:
newTab(null, true); // Search in page
return true; findInPage();
case KeyEvent.KEYCODE_W: return true;
// Close current tab case KeyEvent.KEYCODE_T:
mPresenter.deleteTab(mTabsManager.indexOfCurrentTab()); // Open new tab
return true; newTab(null, true);
case KeyEvent.KEYCODE_Q: return true;
// Close browser case KeyEvent.KEYCODE_W:
closeBrowser(); // Close current tab
return true; mPresenter.deleteTab(mTabsManager.indexOfCurrentTab());
case KeyEvent.KEYCODE_R: return true;
// Refresh current tab case KeyEvent.KEYCODE_Q:
LightningView currentTab = mTabsManager.getCurrentTab(); // Close browser
if (currentTab != null) { closeBrowser();
currentTab.reload(); return true;
} case KeyEvent.KEYCODE_R:
return true; // Refresh current tab
case KeyEvent.KEYCODE_TAB: LightningView currentTab = mTabsManager.getCurrentTab();
int nextIndex = 0; if (currentTab != null) {
if (event.isShiftPressed()) { currentTab.reload();
// Go back one tab
if (mTabsManager.indexOfCurrentTab() > 0) {
nextIndex = mTabsManager.indexOfCurrentTab() - 1;
} else {
nextIndex = mTabsManager.last();
} }
} else { return true;
// Go forward one tab case KeyEvent.KEYCODE_TAB:
if (mTabsManager.indexOfCurrentTab() < mTabsManager.last()) { int nextIndex;
nextIndex = mTabsManager.indexOfCurrentTab() + 1; if (event.isShiftPressed()) {
// Go back one tab
if (mTabsManager.indexOfCurrentTab() > 0) {
nextIndex = mTabsManager.indexOfCurrentTab() - 1;
} else {
nextIndex = mTabsManager.last();
}
} else { } else {
nextIndex = 0; // Go forward one tab
if (mTabsManager.indexOfCurrentTab() < mTabsManager.last()) {
nextIndex = mTabsManager.indexOfCurrentTab() + 1;
} else {
nextIndex = 0;
}
} }
mPresenter.tabChanged(nextIndex);
return true;
}
} else if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
// Highlight search field
mSearch.requestFocus();
mSearch.selectAll();
return true;
} else if (event.isAltPressed()) {
// Alt + tab number
if (KeyEvent.KEYCODE_0 <= event.getKeyCode() && event.getKeyCode() <= KeyEvent.KEYCODE_9) {
int nextIndex;
if (event.getKeyCode() > mTabsManager.last() + KeyEvent.KEYCODE_1 || event.getKeyCode() == KeyEvent.KEYCODE_0) {
nextIndex = mTabsManager.last();
} else {
nextIndex = event.getKeyCode() - KeyEvent.KEYCODE_1;
} }
mPresenter.tabChanged(nextIndex); mPresenter.tabChanged(nextIndex);
return true; return true;
}
} }
} else if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
// Highlight search field
mSearch.requestFocus();
mSearch.selectAll();
return true;
} }
return super.dispatchKeyEvent(event); return super.dispatchKeyEvent(event);
} }
@ -1111,7 +1104,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
@Override @Override
public void showBlockedLocalFileDialog(DialogInterface.OnClickListener listener) { public void showBlockedLocalFileDialog(@NonNull DialogInterface.OnClickListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
Dialog dialog = builder.setCancelable(true) Dialog dialog = builder.setCancelable(true)
.setTitle(R.string.title_warning) .setTitle(R.string.title_warning)
@ -1219,7 +1212,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
void handleNewIntent(Intent intent) { protected void handleNewIntent(Intent intent) {
mPresenter.onNewIntent(intent); mPresenter.onNewIntent(intent);
} }
@ -1243,14 +1236,14 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
return mPresenter.newTab(url, show); return mPresenter.newTab(url, show);
} }
void performExitCleanUp() { protected void performExitCleanUp() {
final LightningView currentTab = mTabsManager.getCurrentTab(); final LightningView currentTab = mTabsManager.getCurrentTab();
if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) { if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) {
WebUtils.clearCache(currentTab.getWebView()); WebUtils.clearCache(currentTab.getWebView());
Log.d(TAG, "Cache Cleared"); Log.d(TAG, "Cache Cleared");
} }
if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) { if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) {
WebUtils.clearHistory(this); WebUtils.clearHistory(this, mHistoryModel);
Log.d(TAG, "History Cleared"); Log.d(TAG, "History Cleared");
} }
if (mPreferences.getClearCookiesExitEnabled() && !isIncognito()) { if (mPreferences.getClearCookiesExitEnabled() && !isIncognito()) {
@ -1369,7 +1362,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
void saveOpenTabs() { protected void saveOpenTabs() {
if (mPreferences.getRestoreLostTabsEnabled()) { if (mPreferences.getRestoreLostTabsEnabled()) {
mTabsManager.saveState(); mTabsManager.saveState();
} }
@ -1533,36 +1526,16 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
@Override @Override
public void updateUrl(@Nullable String url, boolean shortUrl) { public void updateUrl(@Nullable String url, boolean isLoading) {
if (url == null || mSearch == null || mSearch.hasFocus()) { if (url == null || mSearch == null || mSearch.hasFocus()) {
return; return;
} }
final LightningView currentTab = mTabsManager.getCurrentTab(); final LightningView currentTab = mTabsManager.getCurrentTab();
mBookmarksView.handleUpdatedUrl(url); mBookmarksView.handleUpdatedUrl(url);
if (shortUrl && !UrlUtils.isSpecialUrl(url)) {
switch (mPreferences.getUrlBoxContentChoice()) { String currentTitle = currentTab != null ? currentTab.getTitle() : null;
case 0: // Default, show only the domain
url = url.replaceFirst(Constants.HTTP, ""); mSearch.setText(mSearchBoxModel.getDisplayContent(url, currentTitle, isLoading));
url = Utils.getDomainName(url);
mSearch.setText(url);
break;
case 1: // URL, show the entire URL
mSearch.setText(url);
break;
case 2: // Title, show the page's title
if (currentTab != null && !currentTab.getTitle().isEmpty()) {
mSearch.setText(currentTab.getTitle());
} else {
mSearch.setText(mUntitledTitle);
}
break;
}
} else {
if (UrlUtils.isSpecialUrl(url)) {
url = "";
}
mSearch.setText(url);
}
} }
@Override @Override
@ -1579,12 +1552,12 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mProgressBar.setProgress(n); mProgressBar.setProgress(n);
} }
void addItemToHistory(@Nullable final String title, @NonNull final String url) { protected void addItemToHistory(@Nullable final String title, @NonNull final String url) {
if (UrlUtils.isSpecialUrl(url)) { if (UrlUtils.isSpecialUrl(url)) {
return; return;
} }
HistoryModel.visitHistoryItem(url, title) mHistoryModel.visitHistoryItem(url, title)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(new CompletableOnSubscribe() { .subscribe(new CompletableOnSubscribe() {
@Override @Override
@ -1696,7 +1669,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
* @param runnable an optional runnable to run after * @param runnable an optional runnable to run after
* the drawers are closed. * the drawers are closed.
*/ */
void closeDrawers(@Nullable final Runnable runnable) { protected final void closeDrawers(@Nullable final Runnable runnable) {
if (!mDrawerLayout.isDrawerOpen(mDrawerLeft) && !mDrawerLayout.isDrawerOpen(mDrawerRight)) { if (!mDrawerLayout.isDrawerOpen(mDrawerLeft) && !mDrawerLayout.isDrawerOpen(mDrawerRight)) {
if (runnable != null) { if (runnable != null) {
runnable.run(); runnable.run();
@ -2176,8 +2149,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
public void handleBookmarksChange() { public void handleBookmarksChange() {
final LightningView currentTab = mTabsManager.getCurrentTab(); final LightningView currentTab = mTabsManager.getCurrentTab();
if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) if (currentTab != null && UrlUtils.isBookmarkUrl(currentTab.getUrl())) {
&& currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) {
currentTab.loadBookmarkpage(); currentTab.loadBookmarkpage();
} }
if (currentTab != null) { if (currentTab != null) {
@ -2188,8 +2160,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
public void handleDownloadDeleted() { public void handleDownloadDeleted() {
final LightningView currentTab = mTabsManager.getCurrentTab(); final LightningView currentTab = mTabsManager.getCurrentTab();
if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) if (currentTab != null && UrlUtils.isDownloadsUrl(currentTab.getUrl())) {
&& currentTab.getUrl().endsWith(DownloadsPage.FILENAME)) {
currentTab.loadDownloadspage(); currentTab.loadDownloadspage();
} }
if (currentTab != null) { if (currentTab != null) {

7
app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java → app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.browser.activity;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
@ -10,13 +10,14 @@ import android.support.v7.app.AppCompatActivity;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
public abstract class ThemableBrowserActivity extends AppCompatActivity { public abstract class ThemableBrowserActivity extends AppCompatActivity {
@Inject PreferenceManager mPreferences; // TODO: 6/26/17 get rid fo protected reference
@Inject protected PreferenceManager mPreferences;
private int mTheme; private int mTheme;
private boolean mShowTabsInDrawer; private boolean mShowTabsInDrawer;

2
app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java → app/src/main/java/acr/browser/lightning/browser/bookmark/BookmarkUiModel.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.browser.bookmark;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;

18
app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java → app/src/main/java/acr/browser/lightning/browser/fragment/BookmarksFragment.java

@ -1,4 +1,4 @@
package acr.browser.lightning.fragment; package acr.browser.lightning.browser.fragment;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -34,11 +34,11 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.BookmarkUiModel; import acr.browser.lightning.browser.bookmark.BookmarkUiModel;
import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.reading.activity.ReadingActivity;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.browser.TabsManager;
import acr.browser.lightning.animation.AnimationUtils; import acr.browser.lightning.animation.AnimationUtils;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.browser.BookmarksView; import acr.browser.lightning.browser.BookmarksView;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
@ -320,10 +320,10 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
} }
private void setupNavigationButton(@NonNull View view, @IdRes int buttonId, @IdRes int imageId) { private void setupNavigationButton(@NonNull View view, @IdRes int buttonId, @IdRes int imageId) {
FrameLayout frameButton = (FrameLayout) view.findViewById(buttonId); FrameLayout frameButton = view.findViewById(buttonId);
frameButton.setOnClickListener(this); frameButton.setOnClickListener(this);
frameButton.setOnLongClickListener(this); frameButton.setOnLongClickListener(this);
ImageView buttonImage = (ImageView) view.findViewById(imageId); ImageView buttonImage = view.findViewById(imageId);
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
} }
@ -537,8 +537,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
Subscription oldSubscription = mFaviconFetchSubscriptions.get(url); Subscription oldSubscription = mFaviconFetchSubscriptions.get(url);
SubscriptionUtils.safeUnsubscribe(oldSubscription); SubscriptionUtils.safeUnsubscribe(oldSubscription);
final Subscription faviconSubscription = mFaviconModel.faviconForUrl(url, mWebpageBitmap, true) final Subscription faviconSubscription = mFaviconModel.faviconForUrl(url, web.getTitle())
.subscribeOn(Schedulers.worker()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Bitmap>() { .subscribe(new SingleOnSubscribe<Bitmap>() {
@Override @Override

24
app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java → app/src/main/java/acr/browser/lightning/browser/fragment/TabsFragment.java

@ -1,4 +1,4 @@
package acr.browser.lightning.fragment; package acr.browser.lightning.browser.fragment;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -34,12 +34,12 @@ import android.widget.TextView;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.browser.TabsManager;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.browser.TabsView; import acr.browser.lightning.browser.TabsView;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.fragment.anim.HorizontalItemAnimator; import acr.browser.lightning.browser.fragment.anim.HorizontalItemAnimator;
import acr.browser.lightning.fragment.anim.VerticalItemAnimator; import acr.browser.lightning.browser.fragment.anim.VerticalItemAnimator;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.DrawableUtils; import acr.browser.lightning.utils.DrawableUtils;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
@ -130,7 +130,7 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View
} else { } else {
view = inflater.inflate(R.layout.tab_strip, container, false); view = inflater.inflate(R.layout.tab_strip, container, false);
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
ImageView newTab = (ImageView) view.findViewById(R.id.new_tab_button); ImageView newTab = view.findViewById(R.id.new_tab_button);
newTab.setColorFilter(ThemeUtils.getIconDarkThemeColor(getActivity())); newTab.setColorFilter(ThemeUtils.getIconDarkThemeColor(getActivity()));
newTab.setOnClickListener(new View.OnClickListener() { newTab.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -183,7 +183,7 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View
private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId, private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId,
@IdRes final int imageId) { @IdRes final int imageId) {
final View frameButton = root.findViewById(buttonId); final View frameButton = root.findViewById(buttonId);
final ImageView buttonImage = (ImageView) root.findViewById(imageId); final ImageView buttonImage = root.findViewById(imageId);
frameButton.setOnClickListener(this); frameButton.setOnClickListener(this);
frameButton.setOnLongClickListener(this); frameButton.setOnLongClickListener(this);
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
@ -399,11 +399,11 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View
public LightningViewHolder(@NonNull View view) { public LightningViewHolder(@NonNull View view) {
super(view); super(view);
txtTitle = (TextView) view.findViewById(R.id.textTab); txtTitle = view.findViewById(R.id.textTab);
favicon = (ImageView) view.findViewById(R.id.faviconTab); favicon = view.findViewById(R.id.faviconTab);
exit = (ImageView) view.findViewById(R.id.deleteButton); exit = view.findViewById(R.id.deleteButton);
layout = (LinearLayout) view.findViewById(R.id.tab_item_background); layout = view.findViewById(R.id.tab_item_background);
exitButton = (FrameLayout) view.findViewById(R.id.deleteAction); exitButton = view.findViewById(R.id.deleteAction);
exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
exitButton.setOnClickListener(this); exitButton.setOnClickListener(this);

2
app/src/main/java/acr/browser/lightning/fragment/anim/HorizontalItemAnimator.java → app/src/main/java/acr/browser/lightning/browser/fragment/anim/HorizontalItemAnimator.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package acr.browser.lightning.fragment.anim; package acr.browser.lightning.browser.fragment.anim;
import android.animation.TimeInterpolator; import android.animation.TimeInterpolator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;

2
app/src/main/java/acr/browser/lightning/fragment/anim/VerticalItemAnimator.java → app/src/main/java/acr/browser/lightning/browser/fragment/anim/VerticalItemAnimator.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package acr.browser.lightning.fragment.anim; package acr.browser.lightning.browser.fragment.anim;
import android.animation.TimeInterpolator; import android.animation.TimeInterpolator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;

151
app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java

@ -6,8 +6,10 @@ package acr.browser.lightning.constant;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction; import com.anthonycr.bonsai.SingleAction;
@ -24,9 +26,11 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.favicon.FaviconModel;
import acr.browser.lightning.favicon.FaviconUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -74,12 +78,27 @@ public final class BookmarkPage {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
private static final String FOLDER_ICON = "folder.png"; private static final String FOLDER_ICON = "folder.png";
private static final String DEFAULT_ICON = "default.png";
private File mFilesDir; @NonNull
private File mCacheDir; public static File getBookmarkPage(@NonNull Application application, @Nullable String folder) {
String prefix = !TextUtils.isEmpty(folder) ? folder + '-' : "";
return new File(application.getFilesDir(), prefix + FILENAME);
}
@NonNull
private static File getFaviconFile(@NonNull Application application) {
return new File(application.getCacheDir(), FOLDER_ICON);
}
@NonNull
private static File getDefaultIconFile(@NonNull Application application) {
return new File(application.getCacheDir(), DEFAULT_ICON);
}
@Inject Application mApp; @Inject Application mApp;
@Inject BookmarkModel mManager; @Inject BookmarkModel mBookmarkModel;
@Inject FaviconModel mFaviconModel;
@NonNull private final Bitmap mFolderIcon; @NonNull private final Bitmap mFolderIcon;
@NonNull private final String mTitle; @NonNull private final String mTitle;
@ -95,12 +114,11 @@ public final class BookmarkPage {
return Single.create(new SingleAction<String>() { return Single.create(new SingleAction<String>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) {
mCacheDir = mApp.getCacheDir(); cacheIcon(mFolderIcon, getFaviconFile(mApp));
mFilesDir = mApp.getFilesDir(); cacheIcon(mFaviconModel.getDefaultBitmapForString(null), getDefaultIconFile(mApp));
cacheDefaultFolderIcon();
buildBookmarkPage(null); buildBookmarkPage(null);
File bookmarkWebPage = new File(mFilesDir, FILENAME); File bookmarkWebPage = getBookmarkPage(mApp, null);
subscriber.onItem(Constants.FILE + bookmarkWebPage); subscriber.onItem(Constants.FILE + bookmarkWebPage);
subscriber.onComplete(); subscriber.onComplete();
@ -108,13 +126,12 @@ public final class BookmarkPage {
}); });
} }
private void cacheDefaultFolderIcon() { private void cacheIcon(@NonNull Bitmap icon, @NonNull File file) {
FileOutputStream outputStream = null; FileOutputStream outputStream = null;
File image = new File(mCacheDir, FOLDER_ICON);
try { try {
outputStream = new FileOutputStream(image); outputStream = new FileOutputStream(file);
mFolderIcon.compress(Bitmap.CompressFormat.PNG, 100, outputStream); icon.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
mFolderIcon.recycle(); icon.recycle();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -123,52 +140,84 @@ public final class BookmarkPage {
} }
private void buildBookmarkPage(@Nullable final String folder) { private void buildBookmarkPage(@Nullable final String folder) {
mManager.getBookmarksFromFolderSorted(folder) mBookmarkModel.getBookmarksFromFolderSorted(folder)
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override @Override
public void onItem(@Nullable List<HistoryItem> list) { public void onItem(@Nullable final List<HistoryItem> list) {
Preconditions.checkNonNull(list); Preconditions.checkNonNull(list);
final File bookmarkWebPage; if (folder == null) {
if (folder == null || folder.isEmpty()) { mBookmarkModel.getFoldersSorted()
bookmarkWebPage = new File(mFilesDir, FILENAME); .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable List<HistoryItem> item) {
Preconditions.checkNonNull(item);
list.addAll(item);
buildPageHtml(list, null);
}
});
} else { } else {
bookmarkWebPage = new File(mFilesDir, folder + '-' + FILENAME); buildPageHtml(list, folder);
}
final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
final String folderIconPath = Constants.FILE + mCacheDir + '/' + FOLDER_ICON;
for (int n = 0, size = list.size(); n < size; n++) {
final HistoryItem item = list.get(n);
bookmarkBuilder.append(PART1);
if (item.isFolder()) {
final File folderPage = new File(mFilesDir, item.getTitle() + '-' + FILENAME);
bookmarkBuilder.append(Constants.FILE).append(folderPage);
bookmarkBuilder.append(PART2);
bookmarkBuilder.append(folderIconPath);
buildBookmarkPage(item.getTitle());
} else {
bookmarkBuilder.append(item.getUrl());
bookmarkBuilder.append(PART2).append(PART3);
bookmarkBuilder.append(item.getUrl());
}
bookmarkBuilder.append(PART4);
bookmarkBuilder.append(item.getTitle());
bookmarkBuilder.append(PART5);
}
bookmarkBuilder.append(END);
FileWriter bookWriter = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookWriter = new FileWriter(bookmarkWebPage, false);
bookWriter.write(bookmarkBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.close(bookWriter);
} }
} }
}); });
} }
private void buildPageHtml(@NonNull List<HistoryItem> bookmarksAndFolders, @Nullable String folder) {
final File bookmarkWebPage = getBookmarkPage(mApp, folder);
final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
final String folderIconPath = getFaviconFile(mApp).toString();
for (int n = 0, size = bookmarksAndFolders.size(); n < size; n++) {
final HistoryItem item = bookmarksAndFolders.get(n);
bookmarkBuilder.append(PART1);
if (item.isFolder()) {
final File folderPage = getBookmarkPage(mApp, item.getTitle());
bookmarkBuilder.append(Constants.FILE).append(folderPage);
bookmarkBuilder.append(PART2);
bookmarkBuilder.append(folderIconPath);
buildBookmarkPage(item.getTitle());
} else {
Uri bookmarkUri = FaviconUtils.safeUri(item.getUrl());
String faviconFileUrl;
if (bookmarkUri != null) {
File faviconFile = FaviconModel.getFaviconCacheFile(mApp, bookmarkUri);
if (!faviconFile.exists()) {
Bitmap defaultFavicon = mFaviconModel.getDefaultBitmapForString(item.getTitle());
mFaviconModel.cacheFaviconForUrl(defaultFavicon, item.getUrl()).subscribe();
}
faviconFileUrl = Constants.FILE + faviconFile;
} else {
faviconFileUrl = Constants.FILE + getDefaultIconFile(mApp);
}
bookmarkBuilder.append(item.getUrl());
bookmarkBuilder.append(PART2).append(faviconFileUrl);
}
bookmarkBuilder.append(PART4);
bookmarkBuilder.append(item.getTitle());
bookmarkBuilder.append(PART5);
}
bookmarkBuilder.append(END);
FileWriter bookWriter = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookWriter = new FileWriter(bookmarkWebPage, false);
bookWriter.write(bookmarkBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.close(bookWriter);
}
}
} }

6
app/src/main/java/acr/browser/lightning/constant/Constants.java

@ -30,9 +30,9 @@ public final class Constants {
public static final String YANDEX_SEARCH = "https://yandex.ru/yandsearch?lr=21411&text="; public static final String YANDEX_SEARCH = "https://yandex.ru/yandsearch?lr=21411&text=";
// Custom local page schemes // Custom local page schemes
public static final String SCHEME_HOMEPAGE = "about:home"; public static final String SCHEME_HOMEPAGE = Constants.ABOUT + "home";
public static final String SCHEME_BLANK = "about:blank"; public static final String SCHEME_BLANK = Constants.ABOUT + "blank";
public static final String SCHEME_BOOKMARKS = "about:bookmarks"; public static final String SCHEME_BOOKMARKS = Constants.ABOUT + "bookmarks";
// Miscellaneous JavaScript // Miscellaneous JavaScript
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_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)})();";

31
app/src/main/java/acr/browser/lightning/constant/DownloadsPage.java

@ -6,6 +6,7 @@ package acr.browser.lightning.constant;
import android.app.Application; import android.app.Application;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction; import com.anthonycr.bonsai.SingleAction;
@ -20,7 +21,7 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.downloads.DownloadItem; import acr.browser.lightning.database.downloads.DownloadItem;
import acr.browser.lightning.database.downloads.DownloadsModel; import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
@ -29,9 +30,8 @@ import acr.browser.lightning.utils.Utils;
public final class DownloadsPage { public final class DownloadsPage {
/** private static final String TAG = "DownloadsPage";
* The download page standard suffix
*/
public static final String FILENAME = "downloads.html"; public static final String FILENAME = "downloads.html";
private static final String HEADING_1 = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" + private static final String HEADING_1 = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" +
@ -41,10 +41,12 @@ public final class DownloadsPage {
"<meta name=viewport content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>\n" + "<meta name=viewport content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>\n" +
"<title>"; "<title>";
private static final String HEADING_2 = "</title>" + private static final String HEADING_2 = "</title></head><style>body,html {margin: 0px; padding: 0px;}" +
"</head>" + ".box { vertical-align:middle;position:relative; display: block; margin: 0px;padding-left:14px;padding-right:14px;padding-top:9px;padding-bottom:9px; background-color:#fff;border-bottom: 1px solid #d2d2d2;font-family: Arial;color: #444;font-size: 12px;}" +
"<style>body{background:#f5f5f5;}.box{vertical-align:middle;position:relative; display: block; margin: 10px;padding:10px; background-color:#fff;box-shadow: 0px 2px 4px 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>" + ".box a { width: 100%; height: 100%; position: absolute; left: 0; top: 0;}" +
"<body><div id='content'>"; ".black {color: black;font-size: 15px;font-family: Arial; white-space: nowrap; overflow: hidden;margin:auto; text-overflow: ellipsis; -o-text-overflow: ellipsis; -ms-text-overflow: ellipsis;}" +
".font {color: gray;font-size: 10px;font-family: Arial; white-space: nowrap; overflow: hidden;margin:auto; text-overflow: ellipsis; -o-text-overflow: ellipsis; -ms-text-overflow: ellipsis;}" +
"</style><body><div id=\"content\">";
private static final String PART1 = "<div class=box><a href='"; private static final String PART1 = "<div class=box><a href='";
@ -56,7 +58,10 @@ public final class DownloadsPage {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
private File mFilesDir; @NonNull
private static File getDownloadsPageFile(@NonNull Application application) {
return new File(application.getFilesDir(), FILENAME);
}
@Inject Application mApp; @Inject Application mApp;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@ -74,11 +79,9 @@ public final class DownloadsPage {
return Single.create(new SingleAction<String>() { return Single.create(new SingleAction<String>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) {
mFilesDir = mApp.getFilesDir();
buildDownloadsPage(); buildDownloadsPage();
File downloadsWebPage = new File(mFilesDir, FILENAME); File downloadsWebPage = getDownloadsPageFile(mApp);
subscriber.onItem(Constants.FILE + downloadsWebPage); subscriber.onItem(Constants.FILE + downloadsWebPage);
subscriber.onComplete(); subscriber.onComplete();
@ -120,10 +123,10 @@ public final class DownloadsPage {
FileWriter bookWriter = null; FileWriter bookWriter = null;
try { try {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed
bookWriter = new FileWriter(new File(mFilesDir, FILENAME), false); bookWriter = new FileWriter(getDownloadsPageFile(mApp), false);
bookWriter.write(downloadsBuilder.toString()); bookWriter.write(downloadsBuilder.toString());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Unable to write download page to disk", e);
} finally { } finally {
Utils.close(bookWriter); Utils.close(bookWriter);
} }

21
app/src/main/java/acr/browser/lightning/constant/HistoryPage.java

@ -25,7 +25,7 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
@ -61,9 +61,22 @@ public class HistoryPage {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
/**
* Get the file that the history page is stored in
* or should be stored in.
*
* @param application the application used to access the file.
* @return a valid file object, note that the file might not exist.
*/
@NonNull
private static File getHistoryPageFile(@NonNull Application application) {
return new File(application.getFilesDir(), FILENAME);
}
@NonNull private final String mTitle; @NonNull private final String mTitle;
@Inject Application mApp; @Inject Application mApp;
@Inject HistoryModel mHistoryModel;
public HistoryPage() { public HistoryPage() {
BrowserApp.getAppComponent().inject(this); BrowserApp.getAppComponent().inject(this);
@ -77,7 +90,7 @@ public class HistoryPage {
public void onSubscribe(@NonNull final SingleSubscriber<String> subscriber) { public void onSubscribe(@NonNull final SingleSubscriber<String> subscriber) {
final StringBuilder historyBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2); final StringBuilder historyBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2);
HistoryModel.lastHundredVisitedHistoryItems() mHistoryModel.lastHundredVisitedHistoryItems()
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override @Override
public void onItem(@Nullable List<HistoryItem> item) { public void onItem(@Nullable List<HistoryItem> item) {
@ -97,7 +110,7 @@ public class HistoryPage {
} }
historyBuilder.append(END); historyBuilder.append(END);
File historyWebPage = new File(mApp.getFilesDir(), FILENAME); File historyWebPage = getHistoryPageFile(mApp);
FileWriter historyWriter = null; FileWriter historyWriter = null;
try { try {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed
@ -130,7 +143,7 @@ public class HistoryPage {
return Completable.create(new CompletableAction() { return Completable.create(new CompletableAction() {
@Override @Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
File historyWebPage = new File(application.getFilesDir(), FILENAME); File historyWebPage = getHistoryPageFile(application);
if (historyWebPage.exists()) { if (historyWebPage.exists()) {
historyWebPage.delete(); historyWebPage.delete();
} }

133
app/src/main/java/acr/browser/lightning/constant/StartPage.java

@ -17,8 +17,9 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.search.SearchEngineProvider;
import acr.browser.lightning.search.engine.BaseSearchEngine;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
public class StartPage { public class StartPage {
@ -26,38 +27,43 @@ public class StartPage {
public static final String FILENAME = "homepage.html"; public static final String FILENAME = "homepage.html";
private static final String HEAD_1 = "<!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>" + "<head>"
+ "<meta content=\"en-us\" http-equiv=\"Content-Language\" />" + "<meta content=\"en-us\" http-equiv=\"Content-Language\" />"
+ "<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" />" + "<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\">" + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">"
+ "<title>"; + "<title>";
private static final String HEAD_2 = "</title>" private static final String HEAD_2 = "</title>"
+ "</head>" + "</head>"
+ "<style>body{background:#f5f5f5;text-align:center;margin:0px;}#search_input{height:35px; " + "<style>body{background:#f5f5f5;text-align:center;margin:0px;}#search_input{height:35px; "
+ "width:100%;outline:none;border:none;font-size: 16px;background-color:transparent;}" + "width:100%;outline:none;border:none;font-size: 16px;background-color:transparent;}"
+ "span { display: block; overflow: hidden; padding-left:5px;vertical-align:middle;}" + "span { display: block; overflow: hidden; padding-left:5px;vertical-align:middle;}"
+ ".search_bar{display:table;vertical-align:middle;width:90%;height:35px;max-width:500px;margin:0 auto;background-color:#fff;box-shadow: 0px 2px 3px rgba( 0, 0, 0, 0.25 );" + ".search_bar{display:table;vertical-align:middle;width:90%;height:35px;max-width:500px;margin:0 auto;background-color:#fff;box-shadow: 0px 2px 3px rgba( 0, 0, 0, 0.25 );"
+ "font-family: Arial;color: #444;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;}" + "font-family: Arial;color: #444;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;}"
+ "#search_submit{outline:none;height:37px;float:right;color:#404040;font-size:16px;font-weight:bold;border:none;" + "#search_submit{outline:none;height:37px;float:right;color:#404040;font-size:16px;font-weight:bold;border:none;"
+ "background-color:transparent;}.outer { display: table; position: absolute; height: 100%; width: 100%;}" + "background-color:transparent;}.outer { display: table; position: absolute; height: 100%; width: 100%;}"
+ ".middle { display: table-cell; vertical-align: middle;}.inner { margin-left: auto; margin-right: auto; " + ".middle { display: table-cell; vertical-align: middle;}.inner { margin-left: auto; margin-right: auto; "
+ "margin-bottom:10%; width: 100%;}img.smaller{width:50%;max-width:300px;}" + "margin-bottom:10%; width: 100%;}img.smaller{width:50%;max-width:300px;}"
+ ".box { vertical-align:middle;position:relative; display: block; margin: 10px;padding-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;" + ".box { vertical-align:middle;position:relative; display: block; margin: 10px;padding-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;"
+ " background-color:#fff;box-shadow: 0px 3px rgba( 0, 0, 0, 0.1 );font-family: Arial;color: #444;" + " background-color:#fff;box-shadow: 0px 3px rgba( 0, 0, 0, 0.1 );font-family: Arial;color: #444;"
+ "font-size: 12px;-moz-border-radius: 2px;-webkit-border-radius: 2px;" + "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=\""; + "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\" autocomplete=\"off\">" 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\" >" + "<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 = \""; + "</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>"; private static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}</script></body></html>";
@NonNull
public static File getStartPageFile(@NonNull Application application) {
return new File(application.getFilesDir(), FILENAME);
}
@NonNull private final String mTitle; @NonNull private final String mTitle;
@Inject Application mApp; @Inject Application mApp;
@Inject PreferenceManager mPreferenceManager; @Inject SearchEngineProvider mSearchEngineProvider;
public StartPage() { public StartPage() {
BrowserApp.getAppComponent().inject(this); BrowserApp.getAppComponent().inject(this);
@ -71,87 +77,18 @@ public class StartPage {
public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) {
StringBuilder homepageBuilder = new StringBuilder(HEAD_1 + mTitle + HEAD_2); StringBuilder homepageBuilder = new StringBuilder(HEAD_1 + mTitle + HEAD_2);
String icon;
String searchUrl;
switch (mPreferenceManager.getSearchChoice()) {
case 0:
// CUSTOM SEARCH
icon = "file:///android_asset/lightning.png";
searchUrl = mPreferenceManager.getSearchUrl();
break;
case 1:
// GOOGLE_SEARCH;
icon = "file:///android_asset/google.png";
// "https://www.google.com/images/srpr/logo11w.png";
searchUrl = Constants.GOOGLE_SEARCH;
break;
case 2:
// ANDROID SEARCH;
icon = "file:///android_asset/ask.png";
searchUrl = Constants.ASK_SEARCH;
break;
case 3:
// BING_SEARCH;
icon = "file:///android_asset/bing.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Bing_logo_%282013%29.svg/500px-Bing_logo_%282013%29.svg.png";
searchUrl = Constants.BING_SEARCH;
break;
case 4:
// YAHOO_SEARCH;
icon = "file:///android_asset/yahoo.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Yahoo%21_logo.svg/799px-Yahoo%21_logo.svg.png";
searchUrl = Constants.YAHOO_SEARCH;
break;
case 5:
// STARTPAGE_SEARCH;
icon = "file:///android_asset/startpage.png";
// "https://com/graphics/startp_logo.gif";
searchUrl = Constants.STARTPAGE_SEARCH;
break;
case 6:
// STARTPAGE_MOBILE
icon = "file:///android_asset/startpage.png";
// "https://com/graphics/startp_logo.gif";
searchUrl = Constants.STARTPAGE_MOBILE_SEARCH;
break;
case 7:
// DUCK_SEARCH;
icon = "file:///android_asset/duckduckgo.png";
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png";
searchUrl = Constants.DUCK_SEARCH;
break;
case 8:
// DUCK_LITE_SEARCH;
icon = "file:///android_asset/duckduckgo.png";
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png";
searchUrl = Constants.DUCK_LITE_SEARCH;
break;
case 9:
// BAIDU_SEARCH;
icon = "file:///android_asset/baidu.png";
// "http://www.baidu.com/img/bdlogo.gif";
searchUrl = Constants.BAIDU_SEARCH;
break;
case 10:
// YANDEX_SEARCH;
icon = "file:///android_asset/yandex.png";
// "http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Yandex.svg/600px-Yandex.svg.png";
searchUrl = Constants.YANDEX_SEARCH;
break;
default:
// DEFAULT GOOGLE_SEARCH;
icon = "file:///android_asset/google.png";
searchUrl = Constants.GOOGLE_SEARCH;
break;
} BaseSearchEngine currentSearchEngine = mSearchEngineProvider.getCurrentSearchEngine();
String icon = currentSearchEngine.getIconUrl();
String searchUrl = currentSearchEngine.getQueryUrl();
homepageBuilder.append(icon); homepageBuilder.append(icon);
homepageBuilder.append(MIDDLE); homepageBuilder.append(MIDDLE);
homepageBuilder.append(searchUrl); homepageBuilder.append(searchUrl);
homepageBuilder.append(END); homepageBuilder.append(END);
File homepage = new File(mApp.getFilesDir(), FILENAME); File homepage = getStartPageFile(mApp);
FileWriter hWriter = null; FileWriter hWriter = null;
try { try {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed

4
app/src/main/java/acr/browser/lightning/controller/UIController.java

@ -14,7 +14,7 @@ import android.view.View;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.WebChromeClient.CustomViewCallback;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.browser.TabsManager;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.view.LightningView; import acr.browser.lightning.view.LightningView;
@ -28,7 +28,7 @@ public interface UIController {
boolean getUseDarkTheme(); boolean getUseDarkTheme();
void updateUrl(@Nullable String title, boolean shortUrl); void updateUrl(@Nullable String title, boolean isLoading);
void updateProgress(int n); void updateProgress(int n);

44
app/src/main/java/acr/browser/lightning/database/HistoryItem.java

@ -11,21 +11,15 @@ import acr.browser.lightning.utils.Preconditions;
public class HistoryItem implements Comparable<HistoryItem> { public class HistoryItem implements Comparable<HistoryItem> {
// private variables @NonNull private String mUrl = "";
@NonNull @NonNull private String mTitle = "";
private String mUrl = ""; @NonNull private String mFolder = "";
@Nullable private Bitmap mBitmap = null;
@NonNull
private String mTitle = "";
@NonNull
private String mFolder = "";
@Nullable
private Bitmap mBitmap = null;
private int mImageId = 0; private int mImageId = 0;
private int mPosition = 0; private int mPosition = 0;
private boolean mIsFolder = false; private boolean mIsFolder = false;
public HistoryItem() {} public HistoryItem() {}
@ -115,7 +109,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
@Override @Override
public int compareTo(@NonNull HistoryItem another) { public int compareTo(@NonNull HistoryItem another) {
int compare = this.mTitle.compareTo(another.mTitle); int compare = this.mTitle.compareToIgnoreCase(another.mTitle);
if (compare == 0) { if (compare == 0) {
return this.mUrl.compareTo(another.mUrl); return this.mUrl.compareTo(another.mUrl);
} }
@ -123,27 +117,29 @@ public class HistoryItem implements Comparable<HistoryItem> {
} }
@Override @Override
public boolean equals(@Nullable Object object) { public boolean equals(Object o) {
if (this == o) return true;
if (this == object) return true; if (o == null || getClass() != o.getClass()) return false;
if (object == null) return false;
if (!(object instanceof HistoryItem)) return false;
HistoryItem that = (HistoryItem) object; HistoryItem that = (HistoryItem) o;
return mImageId == that.mImageId && return mImageId == that.mImageId &&
this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl) && mPosition == that.mPosition &&
this.mFolder.equals(that.mFolder); mIsFolder == that.mIsFolder &&
mUrl.equals(that.mUrl) &&
mTitle.equals(that.mTitle) &&
mFolder.equals(that.mFolder);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = mUrl.hashCode(); int result = mUrl.hashCode();
result = 31 * result + mImageId;
result = 31 * result + mTitle.hashCode(); result = 31 * result + mTitle.hashCode();
result = 32 * result + mFolder.hashCode(); result = 31 * result + mFolder.hashCode();
result = 31 * result + mImageId; result = 31 * result + mImageId;
result = 31 * result + mPosition;
result = 31 * result + (mIsFolder ? 1 : 0);
return result; return result;
} }

113
app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java

@ -75,7 +75,7 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
*/ */
@WorkerThread @WorkerThread
@NonNull @NonNull
private SQLiteDatabase lazyDatabase() { private synchronized SQLiteDatabase lazyDatabase() {
if (mDatabase == null || !mDatabase.isOpen()) { if (mDatabase == null || !mDatabase.isOpen()) {
mDatabase = getWritableDatabase(); mDatabase = getWritableDatabase();
} }
@ -105,6 +105,13 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
onCreate(db); onCreate(db);
} }
/**
* Binds a {@link HistoryItem} to {@link ContentValues}.
*
* @param bookmarkItem the bookmark to bind.
* @return a valid values object that can be inserted
* into the database.
*/
@NonNull @NonNull
private static ContentValues bindBookmarkToContentValues(@NonNull HistoryItem bookmarkItem) { private static ContentValues bindBookmarkToContentValues(@NonNull HistoryItem bookmarkItem) {
ContentValues contentValues = new ContentValues(4); ContentValues contentValues = new ContentValues(4);
@ -116,6 +123,15 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return contentValues; return contentValues;
} }
/**
* Binds a cursor to a {@link HistoryItem}. This is
* a non consuming operation on the cursor. Note that
* this operation is not safe to perform on a cursor
* unless you know that the cursor is of history items.
*
* @param cursor the cursor to read from.
* @return a valid item containing all the pertinent information.
*/
@NonNull @NonNull
private static HistoryItem bindCursorToHistoryItem(@NonNull Cursor cursor) { private static HistoryItem bindCursorToHistoryItem(@NonNull Cursor cursor) {
HistoryItem bookmark = new HistoryItem(); HistoryItem bookmark = new HistoryItem();
@ -129,6 +145,13 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return bookmark; return bookmark;
} }
/**
* Binds a cursor to a list of {@link HistoryItem}.
* This operation consumes the cursor.
*
* @param cursor the cursor to bind.
* @return a valid list of history items, may be empty.
*/
@NonNull @NonNull
private static List<HistoryItem> bindCursorToHistoryItemList(@NonNull Cursor cursor) { private static List<HistoryItem> bindCursorToHistoryItemList(@NonNull Cursor cursor) {
List<HistoryItem> bookmarks = new ArrayList<>(); List<HistoryItem> bookmarks = new ArrayList<>();
@ -142,13 +165,91 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return bookmarks; return bookmarks;
} }
/**
* URLs can represent the same thing with or without a trailing slash,
* for instance, google.com/ is the same page as google.com. Since these
* can be represented as different bookmarks within the bookmark database,
* it is important to be able to get the alternate version of a URL.
*
* @param url the string that might have a trailing slash.
* @return a string without a trailing slash if the original had one,
* or a string with a trailing slash if the original did not.
*/
@NonNull
private static String alternateSlashUrl(@NonNull String url) {
if (url.endsWith("/")) {
return url.substring(0, url.length() - 1);
} else {
return url + '/';
}
}
/**
* Queries the database for bookmarks with the provided URL. If it
* cannot find any bookmarks with the given URL, it will try to query
* for bookmarks with the {@link #alternateSlashUrl(String)} as its URL.
*
* @param url the URL to query for.
* @return a cursor with bookmarks matching the URL.
*/
@NonNull
private Cursor queryWithOptionalEndSlash(@NonNull String url) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1");
if (cursor.getCount() == 0) {
String alternateUrl = alternateSlashUrl(url);
cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{alternateUrl}, null, null, null, "1");
}
return cursor;
}
/**
* Deletes a bookmark from the database with the provided URL. If it
* cannot find any bookmark with the given URL, it will try to delete
* a bookmark with the {@link #alternateSlashUrl(String)} as its URL.
*
* @param url the URL to delete.
* @return the number of deleted rows.
*/
private int deleteWithOptionalEndSlash(@NonNull String url) {
int deletedRows = lazyDatabase().delete(TABLE_BOOKMARK, KEY_URL + "=?", new String[]{url});
if (deletedRows == 0) {
String alternateUrl = alternateSlashUrl(url);
deletedRows = lazyDatabase().delete(TABLE_BOOKMARK, KEY_URL + "=?", new String[]{alternateUrl});
}
return deletedRows;
}
/**
* Updates a bookmark in the database with the provided URL. If it
* cannot find any bookmark with the given URL, it will try to update
* a bookmark with the {@link #alternateSlashUrl(String)} as its URL.
*
* @param url the URL to update.
* @param contentValues the new values to update to.
* @return the numebr of rows updated.
*/
private int updateWithOptionalEndSlash(@NonNull String url, @NonNull ContentValues contentValues) {
int updatedRows = lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_URL + "=?", new String[]{url});
if (updatedRows == 0) {
String alternateUrl = alternateSlashUrl(url);
updatedRows = lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_URL + "=?", new String[]{alternateUrl});
}
return updatedRows;
}
@NonNull @NonNull
@Override @Override
public Single<HistoryItem> findBookmarkForUrl(@NonNull final String url) { public Single<HistoryItem> findBookmarkForUrl(@NonNull final String url) {
return Single.create(new SingleAction<HistoryItem>() { return Single.create(new SingleAction<HistoryItem>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<HistoryItem> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<HistoryItem> subscriber) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); Cursor cursor = queryWithOptionalEndSlash(url);
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
subscriber.onItem(bindCursorToHistoryItem(cursor)); subscriber.onItem(bindCursorToHistoryItem(cursor));
@ -168,7 +269,7 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return Single.create(new SingleAction<Boolean>() { return Single.create(new SingleAction<Boolean>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); Cursor cursor = queryWithOptionalEndSlash(url);
subscriber.onItem(cursor.moveToFirst()); subscriber.onItem(cursor.moveToFirst());
@ -184,7 +285,7 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return Single.create(new SingleAction<Boolean>() { return Single.create(new SingleAction<Boolean>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{item.getUrl()}, null, null, null, "1"); Cursor cursor = queryWithOptionalEndSlash(item.getUrl());
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
cursor.close(); cursor.close();
@ -229,7 +330,7 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
return Single.create(new SingleAction<Boolean>() { return Single.create(new SingleAction<Boolean>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) {
int rows = lazyDatabase().delete(TABLE_BOOKMARK, KEY_URL + "=?", new String[]{bookmark.getUrl()}); int rows = deleteWithOptionalEndSlash(bookmark.getUrl());
subscriber.onItem(rows > 0); subscriber.onItem(rows > 0);
subscriber.onComplete(); subscriber.onComplete();
@ -290,7 +391,7 @@ public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel
} }
ContentValues contentValues = bindBookmarkToContentValues(newBookmark); ContentValues contentValues = bindBookmarkToContentValues(newBookmark);
lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_URL + "=?", new String[]{oldBookmark.getUrl()}); updateWithOptionalEndSlash(oldBookmark.getUrl(), contentValues);
subscriber.onComplete(); subscriber.onComplete();
} }

30
app/src/main/java/acr/browser/lightning/database/downloads/DownloadItem.java

@ -10,15 +10,9 @@ import acr.browser.lightning.utils.Preconditions;
public class DownloadItem implements Comparable<DownloadItem> { public class DownloadItem implements Comparable<DownloadItem> {
// private variables @NonNull private String mUrl = "";
@NonNull @NonNull private String mTitle = "";
private String mUrl = ""; @NonNull private String mContentSize = "";
@NonNull
private String mTitle = "";
@NonNull
private String mContentSize = "";
public DownloadItem() {} public DownloadItem() {}
@ -66,7 +60,7 @@ public class DownloadItem implements Comparable<DownloadItem> {
@Override @Override
public int compareTo(@NonNull DownloadItem another) { public int compareTo(@NonNull DownloadItem another) {
int compare = this.mTitle.compareTo(another.mTitle); int compare = this.mTitle.compareToIgnoreCase(another.mTitle);
if (compare == 0) { if (compare == 0) {
return this.mUrl.compareTo(another.mUrl); return this.mUrl.compareTo(another.mUrl);
} }
@ -74,23 +68,23 @@ public class DownloadItem implements Comparable<DownloadItem> {
} }
@Override @Override
public boolean equals(@Nullable Object object) { public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == object) return true; DownloadItem that = (DownloadItem) o;
if (object == null) return false;
if (!(object instanceof DownloadItem)) return false;
DownloadItem that = (DownloadItem) object; return mUrl.equals(that.mUrl) &&
mTitle.equals(that.mTitle) &&
mContentSize.equals(that.mContentSize);
return this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl)
&& this.mContentSize.equals(that.mContentSize);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = mUrl.hashCode(); int result = mUrl.hashCode();
result = 31 * result + mTitle.hashCode(); result = 31 * result + mTitle.hashCode();
result = 31 * result + mContentSize.hashCode();
return result; return result;
} }

2
app/src/main/java/acr/browser/lightning/database/downloads/DownloadsDatabase.java

@ -68,7 +68,7 @@ public class DownloadsDatabase extends SQLiteOpenHelper implements DownloadsMode
*/ */
@WorkerThread @WorkerThread
@NonNull @NonNull
private SQLiteDatabase lazyDatabase() { private synchronized SQLiteDatabase lazyDatabase() {
if (mDatabase == null || !mDatabase.isOpen()) { if (mDatabase == null || !mDatabase.isOpen()) {
mDatabase = getWritableDatabase(); mDatabase = getWritableDatabase();
} }

165
app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java

@ -13,6 +13,13 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import com.anthonycr.bonsai.Completable;
import com.anthonycr.bonsai.CompletableAction;
import com.anthonycr.bonsai.CompletableSubscriber;
import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleSubscriber;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -22,9 +29,15 @@ import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
/**
* The disk backed download database.
* See {@link HistoryModel} for method
* documentation.
*/
@Singleton @Singleton
@WorkerThread @WorkerThread
public class HistoryDatabase extends SQLiteOpenHelper { public class HistoryDatabase extends SQLiteOpenHelper implements HistoryModel {
// All Static variables // All Static variables
// Database Version // Database Version
@ -45,9 +58,8 @@ public class HistoryDatabase extends SQLiteOpenHelper {
@Nullable private SQLiteDatabase mDatabase; @Nullable private SQLiteDatabase mDatabase;
@Inject @Inject
HistoryDatabase(@NonNull Application application) { public HistoryDatabase(@NonNull Application application) {
super(application, DATABASE_NAME, null, DATABASE_VERSION); super(application, DATABASE_NAME, null, DATABASE_VERSION);
mDatabase = HistoryDatabase.this.getWritableDatabase();
} }
// Creating Tables // Creating Tables
@ -80,40 +92,108 @@ public class HistoryDatabase extends SQLiteOpenHelper {
@WorkerThread @WorkerThread
@NonNull @NonNull
private SQLiteDatabase lazyDatabase() { private synchronized SQLiteDatabase lazyDatabase() {
if (mDatabase == null || !mDatabase.isOpen()) { if (mDatabase == null || !mDatabase.isOpen()) {
mDatabase = this.getWritableDatabase(); mDatabase = this.getWritableDatabase();
} }
return mDatabase; return mDatabase;
} }
@WorkerThread @NonNull
synchronized void deleteHistory() { @Override
lazyDatabase().delete(TABLE_HISTORY, null, null); public Completable deleteHistory() {
lazyDatabase().close(); return Completable.create(new CompletableAction() {
@Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
lazyDatabase().delete(TABLE_HISTORY, null, null);
lazyDatabase().close();
subscriber.onComplete();
}
});
} }
@WorkerThread @NonNull
synchronized void deleteHistoryItem(@NonNull String url) { @Override
lazyDatabase().delete(TABLE_HISTORY, KEY_URL + " = ?", new String[]{url}); public Completable deleteHistoryItem(@NonNull final String url) {
return Completable.create(new CompletableAction() {
@Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
lazyDatabase().delete(TABLE_HISTORY, KEY_URL + " = ?", new String[]{url});
subscriber.onComplete();
}
});
} }
@WorkerThread @NonNull
synchronized void visitHistoryItem(@NonNull String url, @Nullable String title) { @Override
ContentValues values = new ContentValues(); public Completable visitHistoryItem(@NonNull final String url, @Nullable final String title) {
values.put(KEY_TITLE, title == null ? "" : title); return Completable.create(new CompletableAction() {
values.put(KEY_TIME_VISITED, System.currentTimeMillis()); @Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
ContentValues values = new ContentValues();
values.put(KEY_TITLE, title == null ? "" : title);
values.put(KEY_TIME_VISITED, System.currentTimeMillis());
Cursor cursor = lazyDatabase().query(false, TABLE_HISTORY, new String[]{KEY_URL},
KEY_URL + " = ?", new String[]{url}, null, null, null, "1");
if (cursor.getCount() > 0) {
lazyDatabase().update(TABLE_HISTORY, values, KEY_URL + " = ?", new String[]{url});
} else {
addHistoryItem(new HistoryItem(url, title == null ? "" : title));
}
cursor.close();
}
});
}
Cursor cursor = lazyDatabase().query(false, TABLE_HISTORY, new String[]{KEY_URL}, @NonNull
KEY_URL + " = ?", new String[]{url}, null, null, null, "1"); @Override
public Single<List<HistoryItem>> findHistoryItemsContaining(@NonNull final String query) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> itemList = new ArrayList<>(5);
if (cursor.getCount() > 0) { String search = '%' + query + '%';
lazyDatabase().update(TABLE_HISTORY, values, KEY_URL + " = ?", new String[]{url});
} else {
addHistoryItem(new HistoryItem(url, title == null ? "" : title));
}
cursor.close(); Cursor cursor = lazyDatabase().query(TABLE_HISTORY, null, KEY_TITLE + " LIKE ? OR " + KEY_URL + " LIKE ?",
new String[]{search, search}, null, null, KEY_TIME_VISITED + " DESC", "5");
while (cursor.moveToNext()) {
itemList.add(fromCursor(cursor));
}
cursor.close();
subscriber.onItem(itemList);
subscriber.onComplete();
}
});
}
@NonNull
@Override
public Single<List<HistoryItem>> lastHundredVisitedHistoryItems() {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> itemList = new ArrayList<>(100);
Cursor cursor = lazyDatabase().query(TABLE_HISTORY, null, null, null, null, null, KEY_TIME_VISITED + " DESC", "100");
while (cursor.moveToNext()) {
itemList.add(fromCursor(cursor));
}
cursor.close();
subscriber.onItem(itemList);
subscriber.onComplete();
}
});
} }
@WorkerThread @WorkerThread
@ -140,43 +220,6 @@ public class HistoryDatabase extends SQLiteOpenHelper {
return m; return m;
} }
@WorkerThread
@NonNull
synchronized List<HistoryItem> findItemsContaining(@Nullable String search) {
List<HistoryItem> itemList = new ArrayList<>(5);
if (search == null) {
return itemList;
}
search = '%' + search + '%';
Cursor cursor = lazyDatabase().query(TABLE_HISTORY, null, KEY_TITLE + " LIKE ? OR " + KEY_URL + " LIKE ?",
new String[]{search, search}, null, null, KEY_TIME_VISITED + " DESC", "5");
while (cursor.moveToNext()) {
itemList.add(fromCursor(cursor));
}
cursor.close();
return itemList;
}
@WorkerThread
@NonNull
synchronized List<HistoryItem> getLastHundredItems() {
List<HistoryItem> itemList = new ArrayList<>(100);
Cursor cursor = lazyDatabase().query(TABLE_HISTORY, null, null, null, null, null, KEY_TIME_VISITED + " DESC", "100");
while (cursor.moveToNext()) {
itemList.add(fromCursor(cursor));
}
cursor.close();
return itemList;
}
@WorkerThread @WorkerThread
@NonNull @NonNull
synchronized List<HistoryItem> getAllHistoryItems() { synchronized List<HistoryItem> getAllHistoryItems() {

80
app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java

@ -4,24 +4,19 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.anthonycr.bonsai.Completable; import com.anthonycr.bonsai.Completable;
import com.anthonycr.bonsai.CompletableAction;
import com.anthonycr.bonsai.CompletableSubscriber;
import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleSubscriber;
import java.util.List; import java.util.List;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
/** /**
* A model class providing reactive bindings * An interface that should be used to communicate
* with the underlying history database. * with the history database.
* <p>
* Created by anthonycr on 6/9/17.
*/ */
public final class HistoryModel { public interface HistoryModel {
private HistoryModel() {}
/** /**
* An observable that deletes browser history. * An observable that deletes browser history.
@ -29,18 +24,7 @@ public final class HistoryModel {
* @return a valid observable. * @return a valid observable.
*/ */
@NonNull @NonNull
public static Completable deleteHistory() { Completable deleteHistory();
return Completable.create(new CompletableAction() {
@Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
BrowserApp.getAppComponent()
.historyDatabase()
.deleteHistory();
subscriber.onComplete();
}
});
}
/** /**
* An observable that deletes the history * An observable that deletes the history
@ -50,18 +34,7 @@ public final class HistoryModel {
* @return a valid observable. * @return a valid observable.
*/ */
@NonNull @NonNull
public static Completable deleteHistoryItem(@NonNull final String url) { Completable deleteHistoryItem(@NonNull final String url);
return Completable.create(new CompletableAction() {
@Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
BrowserApp.getAppComponent()
.historyDatabase()
.deleteHistoryItem(url);
subscriber.onComplete();
}
});
}
/** /**
* An observable that visits the URL by * An observable that visits the URL by
@ -74,18 +47,7 @@ public final class HistoryModel {
* @return a valid observable. * @return a valid observable.
*/ */
@NonNull @NonNull
public static Completable visitHistoryItem(@NonNull final String url, @Nullable final String title) { Completable visitHistoryItem(@NonNull final String url, @Nullable final String title);
return Completable.create(new CompletableAction() {
@Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
BrowserApp.getAppComponent()
.historyDatabase()
.visitHistoryItem(url, title);
subscriber.onComplete();
}
});
}
/** /**
* An observable that finds all history items * An observable that finds all history items
@ -100,18 +62,7 @@ public final class HistoryModel {
* a list of history items. * a list of history items.
*/ */
@NonNull @NonNull
public static Single<List<HistoryItem>> findHistoryItemsContaining(@NonNull final String query) { Single<List<HistoryItem>> findHistoryItemsContaining(@NonNull final String query);
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> result = BrowserApp.getAppComponent()
.historyDatabase().findItemsContaining(query);
subscriber.onItem(result);
subscriber.onComplete();
}
});
}
/** /**
* An observable that emits a list of the * An observable that emits a list of the
@ -121,16 +72,5 @@ public final class HistoryModel {
* a list of history items. * a list of history items.
*/ */
@NonNull @NonNull
public static Single<List<HistoryItem>> lastHundredVisitedHistoryItems() { Single<List<HistoryItem>> lastHundredVisitedHistoryItems();
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> result = BrowserApp.getAppComponent()
.historyDatabase().getLastHundredItems();
subscriber.onItem(result);
subscriber.onComplete();
}
});
}
} }

38
app/src/main/java/acr/browser/lightning/app/AppComponent.java → app/src/main/java/acr/browser/lightning/di/AppComponent.java

@ -1,26 +1,30 @@
package acr.browser.lightning.app; package acr.browser.lightning.di;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.activity.BrowserActivity; import acr.browser.lightning.browser.activity.BrowserActivity;
import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.reading.activity.ReadingActivity;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.browser.TabsManager;
import acr.browser.lightning.activity.ThemableBrowserActivity; import acr.browser.lightning.browser.activity.ThemableBrowserActivity;
import acr.browser.lightning.activity.ThemableSettingsActivity; import acr.browser.lightning.settings.activity.ThemableSettingsActivity;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.browser.BrowserPresenter; import acr.browser.lightning.browser.BrowserPresenter;
import acr.browser.lightning.browser.SearchBoxModel;
import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.DownloadsPage; import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.database.history.HistoryDatabase;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.download.DownloadHandler;
import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.download.LightningDownloadListener;
import acr.browser.lightning.fragment.BookmarkSettingsFragment; import acr.browser.lightning.settings.fragment.BookmarkSettingsFragment;
import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.browser.fragment.BookmarksFragment;
import acr.browser.lightning.fragment.DebugSettingsFragment; import acr.browser.lightning.settings.fragment.DebugSettingsFragment;
import acr.browser.lightning.fragment.LightningPreferenceFragment; import acr.browser.lightning.settings.fragment.GeneralSettingsFragment;
import acr.browser.lightning.fragment.PrivacySettingsFragment; import acr.browser.lightning.settings.fragment.LightningPreferenceFragment;
import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.settings.fragment.PrivacySettingsFragment;
import acr.browser.lightning.browser.fragment.TabsFragment;
import acr.browser.lightning.search.SearchEngineProvider;
import acr.browser.lightning.search.SuggestionsAdapter; import acr.browser.lightning.search.SuggestionsAdapter;
import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.view.LightningChromeClient; import acr.browser.lightning.view.LightningChromeClient;
@ -80,6 +84,12 @@ public interface AppComponent {
void inject(LightningChromeClient chromeClient); void inject(LightningChromeClient chromeClient);
HistoryDatabase historyDatabase(); void inject(DownloadHandler downloadHandler);
void inject(SearchBoxModel searchBoxModel);
void inject(SearchEngineProvider searchEngineProvider);
void inject(GeneralSettingsFragment generalSettingsFragment);
} }

24
app/src/main/java/acr/browser/lightning/app/AppModule.java → app/src/main/java/acr/browser/lightning/di/AppModule.java

@ -1,4 +1,4 @@
package acr.browser.lightning.app; package acr.browser.lightning.di;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
@ -8,10 +8,14 @@ import net.i2p.android.ui.I2PAndroidHelper;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.bookmark.BookmarkDatabase; import acr.browser.lightning.database.bookmark.BookmarkDatabase;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.database.downloads.DownloadsDatabase; import acr.browser.lightning.database.downloads.DownloadsDatabase;
import acr.browser.lightning.database.downloads.DownloadsModel; import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.database.history.HistoryDatabase;
import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.download.DownloadHandler;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@ -36,17 +40,31 @@ public class AppModule {
@NonNull @NonNull
@Provides @Provides
@Singleton @Singleton
public BookmarkModel provideBookmarkMode() { public BookmarkModel provideBookmarkModel() {
return new BookmarkDatabase(mApp); return new BookmarkDatabase(mApp);
} }
@NonNull @NonNull
@Provides @Provides
@Singleton @Singleton
public DownloadsModel provideDownloadsMode() { public DownloadsModel provideDownloadsModel() {
return new DownloadsDatabase(mApp); return new DownloadsDatabase(mApp);
} }
@NonNull
@Provides
@Singleton
public HistoryModel providesHistoryModel() {
return new HistoryDatabase(mApp);
}
@NonNull
@Provides
@Singleton
public DownloadHandler provideDownloadHandler() {
return new DownloadHandler();
}
@NonNull @NonNull
@Provides @Provides
@Singleton @Singleton

6
app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.java

@ -82,8 +82,8 @@ public class BrowserDialog {
View layout = LayoutInflater.from(activity).inflate(R.layout.list_dialog, null); View layout = LayoutInflater.from(activity).inflate(R.layout.list_dialog, null);
TextView titleView = (TextView) layout.findViewById(R.id.dialog_title); TextView titleView = layout.findViewById(R.id.dialog_title);
ListView listView = (ListView) layout.findViewById(R.id.dialog_list); ListView listView = layout.findViewById(R.id.dialog_list);
ArrayAdapter<String> adapter = new ArrayAdapter<>(activity, ArrayAdapter<String> adapter = new ArrayAdapter<>(activity,
android.R.layout.simple_list_item_1); android.R.layout.simple_list_item_1);
@ -136,7 +136,7 @@ public class BrowserDialog {
@StringRes int action, @StringRes int action,
@NonNull final EditorListener listener) { @NonNull final EditorListener listener) {
View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_edit_text, null); View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_edit_text, null);
final EditText editText = (EditText) dialogView.findViewById(R.id.dialog_edit_text); final EditText editText = dialogView.findViewById(R.id.dialog_edit_text);
editText.setHint(hint); editText.setHint(hint);
if (currentText != null) { if (currentText != null) {

34
app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java

@ -8,9 +8,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.webkit.URLUtil;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.EditText; import android.widget.EditText;
@ -24,20 +22,20 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.MainActivity; import acr.browser.lightning.MainActivity;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.database.downloads.DownloadItem;
import acr.browser.lightning.database.downloads.DownloadsModel; import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.download.DownloadHandler;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.IntentUtils; import acr.browser.lightning.utils.IntentUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.UrlUtils;
/** /**
* TODO Rename this class it doesn't build dialogs only for bookmarks * TODO Rename this class it doesn't build dialogs only for bookmarks
@ -55,7 +53,9 @@ public class LightningDialogBuilder {
@Inject BookmarkModel mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject DownloadsModel mDownloadsModel; @Inject DownloadsModel mDownloadsModel;
@Inject HistoryModel mHistoryModel;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject DownloadHandler mDownloadHandler;
@Inject @Inject
public LightningDialogBuilder() { public LightningDialogBuilder() {
@ -73,7 +73,7 @@ public class LightningDialogBuilder {
@NonNull final UIController uiController, @NonNull final UIController uiController,
@NonNull final String url) { @NonNull final String url) {
final HistoryItem item; final HistoryItem item;
if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) { if (UrlUtils.isBookmarkUrl(url)) {
// TODO hacky, make a better bookmark mechanism in the future // TODO hacky, make a better bookmark mechanism in the future
final Uri uri = Uri.parse(url); final Uri uri = Uri.parse(url);
final String filename = uri.getLastPathSegment(); final String filename = uri.getLastPathSegment();
@ -91,6 +91,7 @@ public class LightningDialogBuilder {
.subscribe(new SingleOnSubscribe<HistoryItem>() { .subscribe(new SingleOnSubscribe<HistoryItem>() {
@Override @Override
public void onItem(@Nullable HistoryItem historyItem) { public void onItem(@Nullable HistoryItem historyItem) {
// TODO: 6/14/17 figure out solution to case where slashes get appended to root urls causing the item to be null
if (historyItem != null) { if (historyItem != null) {
showLongPressedDialogForBookmarkUrl(activity, uiController, historyItem); showLongPressedDialogForBookmarkUrl(activity, uiController, historyItem);
} }
@ -191,12 +192,12 @@ public class LightningDialogBuilder {
final AlertDialog.Builder editBookmarkDialog = new AlertDialog.Builder(activity); final AlertDialog.Builder editBookmarkDialog = new AlertDialog.Builder(activity);
editBookmarkDialog.setTitle(R.string.title_edit_bookmark); editBookmarkDialog.setTitle(R.string.title_edit_bookmark);
final View dialogLayout = View.inflate(activity, R.layout.dialog_edit_bookmark, null); final View dialogLayout = View.inflate(activity, R.layout.dialog_edit_bookmark, null);
final EditText getTitle = (EditText) dialogLayout.findViewById(R.id.bookmark_title); final EditText getTitle = dialogLayout.findViewById(R.id.bookmark_title);
getTitle.setText(item.getTitle()); getTitle.setText(item.getTitle());
final EditText getUrl = (EditText) dialogLayout.findViewById(R.id.bookmark_url); final EditText getUrl = dialogLayout.findViewById(R.id.bookmark_url);
getUrl.setText(item.getUrl()); getUrl.setText(item.getUrl());
final AutoCompleteTextView getFolder = final AutoCompleteTextView getFolder =
(AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder); dialogLayout.findViewById(R.id.bookmark_folder);
getFolder.setHint(R.string.folder); getFolder.setHint(R.string.folder);
getFolder.setText(item.getFolder()); getFolder.setText(item.getFolder());
@ -332,7 +333,7 @@ public class LightningDialogBuilder {
new BrowserDialog.Item(R.string.dialog_remove_from_history) { new BrowserDialog.Item(R.string.dialog_remove_from_history) {
@Override @Override
public void onClick() { public void onClick() {
HistoryModel.deleteHistoryItem(url) mHistoryModel.deleteHistoryItem(url)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new CompletableOnSubscribe() { .subscribe(new CompletableOnSubscribe() {
@ -384,16 +385,7 @@ public class LightningDialogBuilder {
new BrowserDialog.Item(R.string.dialog_download_image) { new BrowserDialog.Item(R.string.dialog_download_image) {
@Override @Override
public void onClick() { public void onClick() {
Utils.downloadFile(activity, mPreferenceManager, url, userAgent, "attachment"); mDownloadHandler.onDownloadStart(activity, mPreferenceManager, url, userAgent, "attachment", null, "");
mDownloadsModel.addDownloadIfNotExists(new DownloadItem(url, URLUtil.guessFileName(url, null, null), ""))
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
if (item != null && !item)
Log.i(TAG, "error saving download to database");
}
});
} }
}); });
} }

145
app/src/main/java/acr/browser/lightning/download/DownloadHandler.java

@ -23,16 +23,26 @@ import android.webkit.CookieManager;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import com.anthonycr.bonsai.SingleOnSubscribe;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.inject.Inject;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.MainActivity; import acr.browser.lightning.MainActivity;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.downloads.DownloadItem;
import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.FileUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
import acr.browser.lightning.view.LightningView;
/** /**
* Handle download requests * Handle download requests
@ -43,9 +53,11 @@ public class DownloadHandler {
private static final String COOKIE_REQUEST_HEADER = "Cookie"; private static final String COOKIE_REQUEST_HEADER = "Cookie";
public static final String DEFAULT_DOWNLOAD_PATH = @Inject DownloadsModel downloadsModel;
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getPath(); public DownloadHandler() {
BrowserApp.getAppComponent().inject(this);
}
/** /**
* Notify the host application a download should be done, or that the data * Notify the host application a download should be done, or that the data
@ -56,9 +68,10 @@ public class DownloadHandler {
* @param userAgent User agent of the downloading application. * @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present. * @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server * @param mimetype The mimetype of the content reported by the server
* @param contentSize The size of the content
*/ */
public static void onDownloadStart(@NonNull Activity context, @NonNull PreferenceManager manager, String url, String userAgent, public void onDownloadStart(@NonNull Activity context, @NonNull PreferenceManager manager, String url, String userAgent,
@Nullable String contentDisposition, String mimetype) { @Nullable String contentDisposition, String mimetype, String contentSize) {
Log.d(TAG, "DOWNLOAD: Trying to download from URL: " + url); Log.d(TAG, "DOWNLOAD: Trying to download from URL: " + url);
Log.d(TAG, "DOWNLOAD: Content disposition: " + contentDisposition); Log.d(TAG, "DOWNLOAD: Content disposition: " + contentDisposition);
@ -98,7 +111,7 @@ public class DownloadHandler {
} }
} }
} }
onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimetype); onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimetype, contentSize);
} }
// This is to work around the fact that java.net.URI throws Exceptions // This is to work around the fact that java.net.URI throws Exceptions
@ -141,11 +154,12 @@ public class DownloadHandler {
* @param userAgent User agent of the downloading application. * @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present. * @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server * @param mimetype The mimetype of the content reported by the server
* @param contentSize The size of the content
*/ */
/* package */ /* package */
private static void onDownloadStartNoStream(@NonNull final Activity context, @NonNull PreferenceManager preferences, private void onDownloadStartNoStream(@NonNull final Activity context, @NonNull PreferenceManager preferences,
String url, String userAgent, String url, String userAgent,
String contentDisposition, @Nullable String mimetype) { String contentDisposition, @Nullable String mimetype, String contentSize) {
final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype);
// Check to see if we have an SDCard // Check to see if we have an SDCard
@ -198,16 +212,8 @@ public class DownloadHandler {
// or, should it be set to one of several Environment.DIRECTORY* dirs // or, should it be set to one of several Environment.DIRECTORY* dirs
// depending on mimetype? // depending on mimetype?
String location = preferences.getDownloadDirectory(); String location = preferences.getDownloadDirectory();
Uri downloadFolder; location = FileUtils.addNecessarySlashes(location);
location = addNecessarySlashes(location); Uri downloadFolder = Uri.parse(location);
downloadFolder = Uri.parse(location);
File dir = new File(downloadFolder.getPath());
if (!dir.isDirectory() && !dir.mkdirs()) {
// Cannot make the directory
Utils.showSnackbar(context, R.string.problem_location_download);
return;
}
if (!isWriteAccessAvailable(downloadFolder)) { if (!isWriteAccessAvailable(downloadFolder)) {
Utils.showSnackbar(context, R.string.problem_location_download); Utils.showSnackbar(context, R.string.problem_location_download);
@ -254,80 +260,30 @@ public class DownloadHandler {
} }
Utils.showSnackbar(context, context.getString(R.string.download_pending) + ' ' + filename); Utils.showSnackbar(context, context.getString(R.string.download_pending) + ' ' + filename);
} }
}
private static final String sFileName = "test"; // save download in database
private static final String sFileExtension = ".txt"; UIController browserActivity = (UIController) context;
LightningView view = browserActivity.getTabModel().getCurrentTab();
/** if (view != null && !view.isIncognito()) {
* Determine whether there is write access in the given directory. Returns false if a downloadsModel.addDownloadIfNotExists(new DownloadItem(url, filename, contentSize))
* file cannot be created in the directory or if the directory does not exist. .subscribe(new SingleOnSubscribe<Boolean>() {
* @Override
* @param directory the directory to check for write access public void onItem(@Nullable Boolean item) {
* @return returns true if the directory can be written to or is in a directory that can if (item != null && !item)
* be written to. false if there is no write access. Log.i(TAG, "error saving download to database");
*/
public static boolean isWriteAccessAvailable(@Nullable String directory) {
if (directory == null || directory.isEmpty()) {
return false;
}
String dir = addNecessarySlashes(directory);
dir = getFirstRealParentDirectory(dir);
File file = new File(dir + sFileName + sFileExtension);
for (int n = 0; n < 100; n++) {
if (!file.exists()) {
try {
if (file.createNewFile()) {
//noinspection ResultOfMethodCallIgnored
file.delete();
} }
return true; });
} catch (IOException ignored) {
return false;
}
} else {
file = new File(dir + sFileName + '-' + n + sFileExtension);
}
}
return file.canWrite();
}
/**
* Returns the first parent directory of a directory that exists. This is useful
* for subdirectories that do not exist but their parents do.
*
* @param directory the directory to find the first existent parent
* @return the first existent parent
*/
@Nullable
private static String getFirstRealParentDirectory(@Nullable String directory) {
while (true) {
if (directory == null || directory.isEmpty()) {
return "/";
}
directory = addNecessarySlashes(directory);
File file = new File(directory);
if (!file.isDirectory()) {
int indexSlash = directory.lastIndexOf('/');
if (indexSlash > 0) {
String parent = directory.substring(0, indexSlash);
int previousIndex = parent.lastIndexOf('/');
if (previousIndex > 0) {
directory = parent.substring(0, previousIndex);
} else {
return "/";
}
} else {
return "/";
}
} else {
return directory;
}
} }
} }
private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) { private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) {
File file = new File(fileUri.getPath()); File file = new File(fileUri.getPath());
if (!file.isDirectory() && !file.mkdirs()) {
return false;
}
try { try {
if (file.createNewFile()) { if (file.createNewFile()) {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
@ -338,19 +294,4 @@ public class DownloadHandler {
return false; return false;
} }
} }
}
@NonNull
public static String addNecessarySlashes(@Nullable String originalPath) {
if (originalPath == null || originalPath.length() == 0) {
return "/";
}
if (originalPath.charAt(originalPath.length() - 1) != '/') {
originalPath = originalPath + '/';
}
if (originalPath.charAt(0) != '/') {
originalPath = '/' + originalPath;
}
return originalPath;
}
}

19
app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java

@ -7,7 +7,6 @@ import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.format.Formatter; import android.text.format.Formatter;
import android.util.Log; import android.util.Log;
@ -15,13 +14,11 @@ import android.webkit.DownloadListener;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.downloads.DownloadItem;
import acr.browser.lightning.database.downloads.DownloadsModel; import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.grant.PermissionsManager; import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction; import com.anthonycr.grant.PermissionsResultAction;
@ -34,7 +31,7 @@ public class LightningDownloadListener implements DownloadListener {
private final Activity mActivity; private final Activity mActivity;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject DownloadHandler mDownloadHandler;
@Inject DownloadsModel downloadsModel; @Inject DownloadsModel downloadsModel;
public LightningDownloadListener(Activity context) { public LightningDownloadListener(Activity context) {
@ -64,17 +61,7 @@ public class LightningDownloadListener implements DownloadListener {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
switch (which) { switch (which) {
case DialogInterface.BUTTON_POSITIVE: case DialogInterface.BUTTON_POSITIVE:
DownloadHandler.onDownloadStart(mActivity, mPreferenceManager, url, userAgent, mDownloadHandler.onDownloadStart(mActivity, mPreferenceManager, url, userAgent, contentDisposition, mimetype, downloadSize);
contentDisposition, mimetype);
downloadsModel.addDownloadIfNotExists(new DownloadItem(url, fileName, downloadSize))
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
if (item != null && !item)
Log.i(TAG, "error saving download to database");
}
});
break; break;
case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEGATIVE:
break; break;

72
app/src/main/java/acr/browser/lightning/favicon/FaviconModel.java

@ -2,9 +2,13 @@ package acr.browser.lightning.favicon;
import android.app.Application; import android.app.Application;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
@ -22,6 +26,8 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.R;
import acr.browser.lightning.utils.DrawableUtils;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -35,7 +41,7 @@ public class FaviconModel {
private static final String TAG = "FaviconModel"; private static final String TAG = "FaviconModel";
@NonNull private final ImageFetcher mImageFetcher; @NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options();
@NonNull private final Application mApplication; @NonNull private final Application mApplication;
@NonNull private final LruCache<String, Bitmap> mFaviconCache = new LruCache<String, Bitmap>((int) FileUtils.megabytesToBytes(1)) { @NonNull private final LruCache<String, Bitmap> mFaviconCache = new LruCache<String, Bitmap>((int) FileUtils.megabytesToBytes(1)) {
@Override @Override
@ -44,10 +50,12 @@ public class FaviconModel {
} }
}; };
private final int mBookmarkIconSize;
@Inject @Inject
FaviconModel(@NonNull Application application) { FaviconModel(@NonNull Application application) {
mImageFetcher = new ImageFetcher();
mApplication = application; mApplication = application;
mBookmarkIconSize = mApplication.getResources().getDimensionPixelSize(R.dimen.bookmark_item_icon_size);
} }
/** /**
@ -68,6 +76,18 @@ public class FaviconModel {
} }
} }
@NonNull
public Bitmap getDefaultBitmapForString(@Nullable String title) {
Character firstTitleCharacter = !TextUtils.isEmpty(title) ? title.charAt(0) : '?';
@ColorInt int defaultFaviconColor = DrawableUtils.characterToColorHash(firstTitleCharacter, mApplication);
return DrawableUtils.getRoundedLetterImage(firstTitleCharacter,
mBookmarkIconSize,
mBookmarkIconSize,
defaultFaviconColor);
}
/** /**
* Adds a bitmap to the memory cache * Adds a bitmap to the memory cache
* for the given URL. * for the given URL.
@ -93,8 +113,9 @@ public class FaviconModel {
* @param uri the URI to use as a unique identifier. * @param uri the URI to use as a unique identifier.
* @return a valid cache file. * @return a valid cache file.
*/ */
@WorkerThread
@NonNull @NonNull
private static File createFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) { public static File getFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri); FaviconUtils.assertUriSafe(uri);
String hash = String.valueOf(uri.getHost().hashCode()); String hash = String.valueOf(uri.getHost().hashCode());
@ -106,19 +127,13 @@ public class FaviconModel {
* Retrieves the favicon for a URL, * Retrieves the favicon for a URL,
* may be from network or cache. * may be from network or cache.
* *
* @param url the URL that we should retrieve the * @param url The URL that we should retrieve the
* favicon for. * favicon for.
* @param defaultFavicon the default favicon if no * @param title The title for the web page.
* favicon is found.
* @param allowGoogleService true to allow grabbing favicons
* from Google, false otherwise.
* @return an observable that emits a bitmap if one is found,
* or the default if none was found.
*/ */
@NonNull @NonNull
public Single<Bitmap> faviconForUrl(@NonNull final String url, public Single<Bitmap> faviconForUrl(@NonNull final String url,
@NonNull final Bitmap defaultFavicon, @NonNull final String title) {
final boolean allowGoogleService) {
return Single.create(new SingleAction<Bitmap>() { return Single.create(new SingleAction<Bitmap>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) {
@ -126,7 +141,7 @@ public class FaviconModel {
if (uri == null) { if (uri == null) {
Bitmap newFavicon = Utils.padFavicon(defaultFavicon); Bitmap newFavicon = Utils.padFavicon(getDefaultBitmapForString(title));
subscriber.onItem(newFavicon); subscriber.onItem(newFavicon);
subscriber.onComplete(); subscriber.onComplete();
@ -134,19 +149,19 @@ public class FaviconModel {
return; return;
} }
File faviconCacheFile = createFaviconCacheFile(mApplication, uri); File faviconCacheFile = getFaviconCacheFile(mApplication, uri);
Bitmap favicon = getFaviconFromMemCache(url); Bitmap favicon = getFaviconFromMemCache(url);
if (faviconCacheFile.exists() && favicon == null) { if (faviconCacheFile.exists() && favicon == null) {
favicon = mImageFetcher.retrieveFaviconFromCache(faviconCacheFile); favicon = BitmapFactory.decodeFile(faviconCacheFile.getPath(), mLoaderOptions);
if (favicon != null) {
addFaviconToMemCache(url, favicon);
}
} }
if (favicon == null) { if (favicon != null) {
// TODO: 6/5/17 figure out if optimistic favicon retrieval should be added back or dropped
// favicon = mImageFetcher.retrieveBitmapFromDomain(uri);
} else {
Bitmap newFavicon = Utils.padFavicon(favicon); Bitmap newFavicon = Utils.padFavicon(favicon);
subscriber.onItem(newFavicon); subscriber.onItem(newFavicon);
@ -155,18 +170,7 @@ public class FaviconModel {
return; return;
} }
// if (favicon == null && allowGoogleService) { favicon = getDefaultBitmapForString(title);
// favicon = mImageFetcher.retrieveBitmapFromGoogle(uri);
// }
// if (favicon != null) {
// addFaviconToMemCache(url, favicon);
// cacheFaviconForUrl(favicon, url).subscribe();
// }
// if (favicon == null) {
favicon = defaultFavicon;
// }
Bitmap newFavicon = Utils.padFavicon(favicon); Bitmap newFavicon = Utils.padFavicon(favicon);
@ -201,7 +205,7 @@ public class FaviconModel {
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
File image = createFaviconCacheFile(mApplication, uri); File image = getFaviconCacheFile(mApplication, uri);
fos = new FileOutputStream(image); fos = new FileOutputStream(image);
favicon.compress(Bitmap.CompressFormat.PNG, 100, fos); favicon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush(); fos.flush();

4
app/src/main/java/acr/browser/lightning/favicon/FaviconUtils.java

@ -8,9 +8,9 @@ import android.text.TextUtils;
/** /**
* Simple utils for favicon fetching. * Simple utils for favicon fetching.
*/ */
class FaviconUtils { public class FaviconUtils {
@Nullable @Nullable
static Uri safeUri(@NonNull String url) { public static Uri safeUri(@NonNull String url) {
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
return null; return null;
} }

110
app/src/main/java/acr/browser/lightning/favicon/ImageFetcher.java

@ -1,110 +0,0 @@
package acr.browser.lightning.favicon;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import acr.browser.lightning.utils.Utils;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* An image fetcher that creates image
* loading requests on demand.
*/
class ImageFetcher {
private static final String TAG = "ImageFetcher";
@NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options();
@NonNull private final OkHttpClient mHttpClient = new OkHttpClient();
ImageFetcher() {}
@Nullable
Bitmap retrieveFaviconFromCache(@NonNull File cacheFile) {
return BitmapFactory.decodeFile(cacheFile.getPath(), mLoaderOptions);
}
@Nullable
Bitmap retrieveBitmapFromDomain(@NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri);
String faviconUrlGuess = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico";
return retrieveBitmapFromUrl(faviconUrlGuess);
}
@Nullable
Bitmap retrieveBitmapFromGoogle(@NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri);
String googleFaviconUrl = "https://www.google.com/s2/favicons?domain_url=" + uri.getHost();
return retrieveBitmapFromUrl(googleFaviconUrl);
}
@Nullable
private Bitmap retrieveBitmapFromUrl(@NonNull String url) {
Bitmap icon = null;
InputStream boundsStream = null;
InputStream iconStream = null;
try {
mLoaderOptions.inSampleSize = 1;
mLoaderOptions.inJustDecodeBounds = true;
Request imageRequest = new Request.Builder().url(url).build();
Response boundsResponse = mHttpClient.newCall(imageRequest).execute();
ResponseBody boundsBody = boundsResponse.body();
if (boundsBody == null) {
return null;
}
boundsStream = boundsBody.byteStream();
BitmapFactory.decodeStream(boundsStream, null, mLoaderOptions);
boundsBody.close();
int size = Utils.dpToPx(24);
mLoaderOptions.inSampleSize = Utils.calculateInSampleSize(mLoaderOptions, size, size);
mLoaderOptions.inJustDecodeBounds = false;
Response imageResponse = mHttpClient.newCall(imageRequest).execute();
ResponseBody imageBody = imageResponse.body();
if (imageBody == null) {
return null;
}
iconStream = imageBody.byteStream();
icon = BitmapFactory.decodeStream(iconStream, null, mLoaderOptions);
imageBody.close();
} catch (IOException exception) {
Log.d(TAG, "Unable to download icon: " + url);
} finally {
Utils.close(boundsStream);
Utils.close(iconStream);
}
return icon;
}
}

10
app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.java

@ -1,10 +1,8 @@
package acr.browser.lightning.interpolator; package acr.browser.lightning.interpolator;
import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.view.animation.DecelerateInterpolator; import android.support.v4.view.animation.PathInterpolatorCompat;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
/** /**
* Bezier decelerate curve similar to iOS. * Bezier decelerate curve similar to iOS.
@ -17,11 +15,7 @@ public class BezierDecelerateInterpolator implements Interpolator {
private static final Interpolator PATH_INTERPOLATOR; private static final Interpolator PATH_INTERPOLATOR;
static { static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { PATH_INTERPOLATOR = PathInterpolatorCompat.create(0.25f, 0.1f, 0.25f, 1);
PATH_INTERPOLATOR = new PathInterpolator(0.25f, 0.1f, 0.25f, 1);
} else {
PATH_INTERPOLATOR = new DecelerateInterpolator();
}
} }
@Override @Override

4
app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java

@ -9,7 +9,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.download.DownloadHandler; import acr.browser.lightning.utils.FileUtils;
@Singleton @Singleton
public class PreferenceManager { public class PreferenceManager {
@ -150,7 +150,7 @@ public class PreferenceManager {
@NonNull @NonNull
public String getDownloadDirectory() { public String getDownloadDirectory() {
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, DownloadHandler.DEFAULT_DOWNLOAD_PATH); return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, FileUtils.DEFAULT_DOWNLOAD_PATH);
} }
public int getFlashSupport() { public int getFlashSupport() {

4
app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java

@ -376,8 +376,8 @@ public class HtmlFetcher {
*/ */
private String getResolvedUrl(String urlAsString, int timeout, private String getResolvedUrl(String urlAsString, int timeout,
int num_redirects) { int num_redirects) {
String newUrl = null; String newUrl;
int responseCode = -1; int responseCode;
try { try {
HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, true); HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, true);
// force no follow // force no follow

6
app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java → app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.reading.activity;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.app.Dialog; import android.app.Dialog;
@ -26,7 +26,7 @@ import android.widget.TextView;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
@ -297,7 +297,7 @@ public class ReadingActivity extends AppCompatActivity {
case R.id.text_size_item: case R.id.text_size_item:
View view = LayoutInflater.from(this).inflate(R.layout.dialog_seek_bar, null); View view = LayoutInflater.from(this).inflate(R.layout.dialog_seek_bar, null);
final SeekBar bar = (SeekBar) view.findViewById(R.id.text_size_seekbar); final SeekBar bar = view.findViewById(R.id.text_size_seekbar);
bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override @Override

112
app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.java

@ -0,0 +1,112 @@
package acr.browser.lightning.search;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.search.engine.AskSearch;
import acr.browser.lightning.search.engine.BaiduSearch;
import acr.browser.lightning.search.engine.BaseSearchEngine;
import acr.browser.lightning.search.engine.BingSearch;
import acr.browser.lightning.search.engine.CustomSearch;
import acr.browser.lightning.search.engine.DuckLiteSearch;
import acr.browser.lightning.search.engine.DuckSearch;
import acr.browser.lightning.search.engine.GoogleSearch;
import acr.browser.lightning.search.engine.StartPageMobileSearch;
import acr.browser.lightning.search.engine.StartPageSearch;
import acr.browser.lightning.search.engine.YahooSearch;
import acr.browser.lightning.search.engine.YandexSearch;
/**
* The model that provides the search engine based
* on the user's preference.
*/
public class SearchEngineProvider {
@Inject PreferenceManager mPreferenceManager;
@Inject
public SearchEngineProvider() {
BrowserApp.getAppComponent().inject(this);
}
@NonNull
public BaseSearchEngine getCurrentSearchEngine() {
switch (mPreferenceManager.getSearchChoice()) {
case 0:
return new CustomSearch(mPreferenceManager.getSearchUrl());
case 1:
default:
return new GoogleSearch();
case 2:
return new AskSearch();
case 3:
return new BingSearch();
case 4:
return new YahooSearch();
case 5:
return new StartPageSearch();
case 6:
return new StartPageMobileSearch();
case 7:
return new DuckSearch();
case 8:
return new DuckLiteSearch();
case 9:
return new BaiduSearch();
case 10:
return new YandexSearch();
}
}
public int mapSearchEngineToPreferenceIndex(@NonNull BaseSearchEngine searchEngine) {
if (searchEngine instanceof CustomSearch) {
return 0;
} else if (searchEngine instanceof GoogleSearch) {
return 1;
} else if (searchEngine instanceof AskSearch) {
return 2;
} else if (searchEngine instanceof BingSearch) {
return 3;
} else if (searchEngine instanceof YahooSearch) {
return 4;
} else if (searchEngine instanceof StartPageSearch) {
return 5;
} else if (searchEngine instanceof StartPageMobileSearch) {
return 6;
} else if (searchEngine instanceof DuckSearch) {
return 7;
} else if (searchEngine instanceof DuckLiteSearch) {
return 8;
} else if (searchEngine instanceof BaiduSearch) {
return 9;
} else if (searchEngine instanceof YandexSearch) {
return 10;
} else {
throw new UnsupportedOperationException("Unknown search engine provided: " + searchEngine.getClass());
}
}
@NonNull
public List<BaseSearchEngine> getAllSearchEngines() {
return new ArrayList<BaseSearchEngine>(11) {{
add(new CustomSearch(mPreferenceManager.getSearchUrl()));
add(new GoogleSearch());
add(new AskSearch());
add(new BingSearch());
add(new YahooSearch());
add(new StartPageSearch());
add(new StartPageMobileSearch());
add(new DuckSearch());
add(new DuckLiteSearch());
add(new BaiduSearch());
add(new YandexSearch());
}};
}
}

18
app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java

@ -37,7 +37,7 @@ import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.database.history.HistoryModel;
@ -67,6 +67,7 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
@Inject BookmarkModel mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject HistoryModel mHistoryModel;
@Inject Application mApplication; @Inject Application mApplication;
private final List<HistoryItem> mAllBookmarks = new ArrayList<>(5); private final List<HistoryItem> mAllBookmarks = new ArrayList<>(5);
@ -136,9 +137,9 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
private static class SuggestionHolder { private static class SuggestionHolder {
SuggestionHolder(@NonNull View view) { SuggestionHolder(@NonNull View view) {
mTitle = (TextView) view.findViewById(R.id.title); mTitle = view.findViewById(R.id.title);
mUrl = (TextView) view.findViewById(R.id.url); mUrl = view.findViewById(R.id.url);
mImage = (ImageView) view.findViewById(R.id.suggestionIcon); mImage = view.findViewById(R.id.suggestionIcon);
} }
@NonNull final ImageView mImage; @NonNull final ImageView mImage;
@ -197,7 +198,7 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
@NonNull @NonNull
@Override @Override
public Filter getFilter() { public Filter getFilter() {
return new SearchFilter(this); return new SearchFilter(this, mHistoryModel);
} }
private synchronized void publishResults(@NonNull List<HistoryItem> list) { private synchronized void publishResults(@NonNull List<HistoryItem> list) {
@ -318,9 +319,12 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
private static class SearchFilter extends Filter { private static class SearchFilter extends Filter {
@NonNull private final SuggestionsAdapter mSuggestionsAdapter; @NonNull private final SuggestionsAdapter mSuggestionsAdapter;
@NonNull private final HistoryModel mHistoryModel;
SearchFilter(@NonNull SuggestionsAdapter suggestionsAdapter) { SearchFilter(@NonNull SuggestionsAdapter suggestionsAdapter,
@NonNull HistoryModel historyModel) {
mSuggestionsAdapter = suggestionsAdapter; mSuggestionsAdapter = suggestionsAdapter;
mHistoryModel = historyModel;
} }
@NonNull @NonNull
@ -355,7 +359,7 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
} }
}); });
HistoryModel.findHistoryItemsContaining(query) mHistoryModel.findHistoryItemsContaining(query)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {

67
app/src/main/java/acr/browser/lightning/search/SuggestionsManager.java

@ -1,67 +0,0 @@
package acr.browser.lightning.search;
import android.app.Application;
import android.support.annotation.NonNull;
import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleSubscriber;
import java.util.List;
import acr.browser.lightning.database.HistoryItem;
class SuggestionsManager {
private static volatile boolean sIsTaskExecuting;
static boolean isRequestInProgress() {
return sIsTaskExecuting;
}
@NonNull
static Single<List<HistoryItem>> createGoogleQueryObservable(@NonNull final String query,
@NonNull final Application application) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull final SingleSubscriber<List<HistoryItem>> subscriber) {
sIsTaskExecuting = true;
List<HistoryItem> results = new GoogleSuggestionsModel(application).getResults(query);
subscriber.onItem(results);
subscriber.onComplete();
sIsTaskExecuting = false;
}
});
}
@NonNull
static Single<List<HistoryItem>> createBaiduQueryObservable(@NonNull final String query,
@NonNull final Application application) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull final SingleSubscriber<List<HistoryItem>> subscriber) {
sIsTaskExecuting = true;
List<HistoryItem> results = new BaiduSuggestionsModel(application).getResults(query);
subscriber.onItem(results);
subscriber.onComplete();
sIsTaskExecuting = false;
}
});
}
@NonNull
static Single<List<HistoryItem>> createDuckQueryObservable(@NonNull final String query,
@NonNull final Application application) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull final SingleSubscriber<List<HistoryItem>> subscriber) {
sIsTaskExecuting = true;
List<HistoryItem> results = new DuckSuggestionsModel(application).getResults(query);
subscriber.onItem(results);
subscriber.onComplete();
sIsTaskExecuting = false;
}
});
}
}

46
app/src/main/java/acr/browser/lightning/search/SuggestionsManager.kt

@ -0,0 +1,46 @@
package acr.browser.lightning.search
import acr.browser.lightning.database.HistoryItem
import acr.browser.lightning.search.suggestions.BaiduSuggestionsModel
import acr.browser.lightning.search.suggestions.DuckSuggestionsModel
import acr.browser.lightning.search.suggestions.GoogleSuggestionsModel
import android.app.Application
import com.anthonycr.bonsai.Single
import com.anthonycr.bonsai.SingleAction
internal object SuggestionsManager {
@JvmStatic
@Volatile var isRequestInProgress: Boolean = false
@JvmStatic
fun createGoogleQueryObservable(query: String, application: Application) =
Single.create(SingleAction<List<HistoryItem>> { subscriber ->
isRequestInProgress = true
val results = GoogleSuggestionsModel(application).fetchResults(query)
subscriber.onItem(results)
subscriber.onComplete()
isRequestInProgress = false
})
@JvmStatic
fun createBaiduQueryObservable(query: String, application: Application) =
Single.create(SingleAction<List<HistoryItem>> { subscriber ->
isRequestInProgress = true
val results = BaiduSuggestionsModel(application).fetchResults(query)
subscriber.onItem(results)
subscriber.onComplete()
isRequestInProgress = false
})
@JvmStatic
fun createDuckQueryObservable(query: String, application: Application) =
Single.create(SingleAction<List<HistoryItem>> { subscriber ->
isRequestInProgress = true
val results = DuckSuggestionsModel(application).fetchResults(query)
subscriber.onItem(results)
subscriber.onComplete()
isRequestInProgress = false
})
}

13
app/src/main/java/acr/browser/lightning/search/engine/AskSearch.kt

@ -0,0 +1,13 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Ask search engine.
*/
class AskSearch : BaseSearchEngine(
"file:///android_asset/ask.png",
Constants.ASK_SEARCH,
R.string.search_engine_ask
)

15
app/src/main/java/acr/browser/lightning/search/engine/BaiduSearch.kt

@ -0,0 +1,15 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Baidu search engine.
*
* See http://www.baidu.com/img/bdlogo.gif for the icon.
*/
class BaiduSearch : BaseSearchEngine(
"file:///android_asset/baidu.png",
Constants.BAIDU_SEARCH,
R.string.search_engine_baidu
)

15
app/src/main/java/acr/browser/lightning/search/engine/BaseSearchEngine.kt

@ -0,0 +1,15 @@
package acr.browser.lightning.search.engine
import android.support.annotation.StringRes
/**
* A class representative of a search engine.
*
* Contains three key pieces of information:
* * The icon shown for the search engine, should point to a local assets URL.
* * The query URL for the search engine, the query will be appended to the end.
* * The title string resource for the search engine.
*/
open class BaseSearchEngine internal constructor(val iconUrl: String,
val queryUrl: String,
@StringRes val titleRes: Int)

16
app/src/main/java/acr/browser/lightning/search/engine/BingSearch.kt

@ -0,0 +1,16 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Bing search engine.
*
* See http://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Bing_logo_%282013%29.svg/500px-Bing_logo_%282013%29.svg.png
* for the icon.
*/
class BingSearch : BaseSearchEngine(
"file:///android_asset/bing.png",
Constants.BING_SEARCH,
R.string.search_engine_bing
)

12
app/src/main/java/acr/browser/lightning/search/engine/CustomSearch.kt

@ -0,0 +1,12 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
/**
* A custom search engine.
*/
class CustomSearch(queryUrl: String) : BaseSearchEngine(
"file:///android_asset/lightning.png",
queryUrl,
R.string.search_engine_custom
)

15
app/src/main/java/acr/browser/lightning/search/engine/DuckLiteSearch.kt

@ -0,0 +1,15 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The DuckDuckGo Lite search engine.
*
* See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon.
*/
class DuckLiteSearch : BaseSearchEngine(
"file:///android_asset/duckduckgo.png",
Constants.DUCK_LITE_SEARCH,
R.string.search_engine_duckduckgo_lite
)

15
app/src/main/java/acr/browser/lightning/search/engine/DuckSearch.kt

@ -0,0 +1,15 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The DuckDuckGo search engine.
*
* See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon.
*/
class DuckSearch : BaseSearchEngine(
"file:///android_asset/duckduckgo.png",
Constants.DUCK_SEARCH,
R.string.search_engine_duckduckgo
)

15
app/src/main/java/acr/browser/lightning/search/engine/GoogleSearch.kt

@ -0,0 +1,15 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Google search engine.
*
* See https://www.google.com/images/srpr/logo11w.png for the icon.
*/
class GoogleSearch : BaseSearchEngine(
"file:///android_asset/google.png",
Constants.GOOGLE_SEARCH,
R.string.search_engine_google
)

13
app/src/main/java/acr/browser/lightning/search/engine/StartPageMobileSearch.kt

@ -0,0 +1,13 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The StartPage mobile search engine.
*/
class StartPageMobileSearch : BaseSearchEngine(
"file:///android_asset/startpage.png",
Constants.STARTPAGE_MOBILE_SEARCH,
R.string.search_engine_startpage_mobile
)

13
app/src/main/java/acr/browser/lightning/search/engine/StartPageSearch.kt

@ -0,0 +1,13 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The StartPage search engine.
*/
class StartPageSearch : BaseSearchEngine(
"file:///android_asset/startpage.png",
Constants.STARTPAGE_SEARCH,
R.string.search_engine_startpage
)

16
app/src/main/java/acr/browser/lightning/search/engine/YahooSearch.kt

@ -0,0 +1,16 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Yahoo search engine.
*
* See http://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Yahoo%21_logo.svg/799px-Yahoo%21_logo.svg.png
* for the icon.
*/
class YahooSearch : BaseSearchEngine(
"file:///android_asset/yahoo.png",
Constants.YAHOO_SEARCH,
R.string.search_engine_yahoo
)

16
app/src/main/java/acr/browser/lightning/search/engine/YandexSearch.kt

@ -0,0 +1,16 @@
package acr.browser.lightning.search.engine
import acr.browser.lightning.R
import acr.browser.lightning.constant.Constants
/**
* The Yandex search engine.
*
* See http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Yandex.svg/600px-Yandex.svg.png
* for the icon.
*/
class YandexSearch : BaseSearchEngine(
"file:///android_asset/yandex.png",
Constants.YANDEX_SEARCH,
R.string.search_engine_yandex
)

22
app/src/main/java/acr/browser/lightning/search/BaiduSuggestionsModel.java → app/src/main/java/acr/browser/lightning/search/suggestions/BaiduSuggestionsModel.java

@ -1,11 +1,9 @@
package acr.browser.lightning.search; package acr.browser.lightning.search.suggestions;
import android.app.Application; import android.app.Application;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
import org.xmlpull.v1.XmlPullParser;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
@ -14,23 +12,23 @@ import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
// http://unionsug.baidu.com/su?wd=encodeURIComponent(U) /**
// http://suggestion.baidu.com/s?wd=encodeURIComponent(U)&action=opensearch * The search suggestions provider for the Baidu search engine.
*/
public class BaiduSuggestionsModel extends BaseSuggestionsModel {
class BaiduSuggestionsModel extends BaseSuggestionsModel {
@NonNull private static final String ENCODING = "UTF-8"; @NonNull private static final String ENCODING = "UTF-8";
@Nullable private static XmlPullParser sXpp;
@NonNull private final String mSearchSubtitle; @NonNull private final String mSearchSubtitle;
BaiduSuggestionsModel(@NonNull Application application) { public BaiduSuggestionsModel(@NonNull Application application) {
super(application, ENCODING); super(application, ENCODING);
mSearchSubtitle = application.getString(R.string.suggestion); mSearchSubtitle = application.getString(R.string.suggestion);
} }
@NonNull @NonNull
protected String createQueryUrl(@NonNull String query, @NonNull String language) { protected String createQueryUrl(@NonNull String query, @NonNull String language) {
// see http://unionsug.baidu.com/su?wd=encodeURIComponent(U)
// see http://suggestion.baidu.com/s?wd=encodeURIComponent(U)&action=opensearch
return "http://suggestion.baidu.com/s?wd=" + query + "&action=opensearch"; return "http://suggestion.baidu.com/s?wd=" + query + "&action=opensearch";
} }
@ -39,12 +37,14 @@ class BaiduSuggestionsModel extends BaseSuggestionsModel {
String content = FileUtils.readStringFromStream(inputStream, "GBK"); String content = FileUtils.readStringFromStream(inputStream, "GBK");
JSONArray respArray = new JSONArray(content); JSONArray respArray = new JSONArray(content);
JSONArray jsonArray = respArray.getJSONArray(1); JSONArray jsonArray = respArray.getJSONArray(1);
int counter = 0; int counter = 0;
for (int n = 0, size = jsonArray.length(); n < size; n++) { for (int n = 0, size = jsonArray.length(); n < size; n++) {
String suggestion = jsonArray.getString(n); String suggestion = jsonArray.getString(n);
results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"',
suggestion, R.drawable.ic_search)); suggestion, R.drawable.ic_search));
counter++; counter++;
if (counter >= MAX_RESULTS) { if (counter >= MAX_RESULTS) {
break; break;
} }

60
app/src/main/java/acr/browser/lightning/search/BaseSuggestionsModel.java → app/src/main/java/acr/browser/lightning/search/suggestions/BaseSuggestionsModel.java

@ -1,4 +1,4 @@
package acr.browser.lightning.search; package acr.browser.lightning.search.suggestions;
import android.app.Application; import android.app.Application;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -28,7 +28,12 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
abstract class BaseSuggestionsModel { /**
* The base search suggestions API. Provides common
* fetching and caching functionality for each potential
* suggestions provider.
*/
public abstract class BaseSuggestionsModel {
private static final String TAG = "BaseSuggestionsModel"; private static final String TAG = "BaseSuggestionsModel";
@ -41,9 +46,23 @@ abstract class BaseSuggestionsModel {
@NonNull private final String mEncoding; @NonNull private final String mEncoding;
@NonNull private final String mLanguage; @NonNull private final String mLanguage;
/**
* Create a URL for the given query in the given language.
*
* @param query the query that was made.
* @param language the locale of the user.
* @return should return a URL that can be fetched using a GET.
*/
@NonNull @NonNull
protected abstract String createQueryUrl(@NonNull String query, @NonNull String language); protected abstract String createQueryUrl(@NonNull String query, @NonNull String language);
/**
* Parse the results of an input stream into a list of {@link HistoryItem}.
*
* @param inputStream the raw input to parse.
* @param results the list to populate.
* @throws Exception throw an exception if anything goes wrong.
*/
protected abstract void parseResults(@NonNull InputStream inputStream, @NonNull List<HistoryItem> results) throws Exception; protected abstract void parseResults(@NonNull InputStream inputStream, @NonNull List<HistoryItem> results) throws Exception;
BaseSuggestionsModel(@NonNull Application application, @NonNull String encoding) { BaseSuggestionsModel(@NonNull Application application, @NonNull String encoding) {
@ -57,23 +76,25 @@ abstract class BaseSuggestionsModel {
mCacheControl = new CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build(); mCacheControl = new CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build();
} }
/**
* Retrieves the results for a query.
*
* @param rawQuery the raw query to retrieve the results for.
* @return a list of history items for the query.
*/
@NonNull @NonNull
private static String getLanguage() { public final List<HistoryItem> fetchResults(@NonNull final String rawQuery) {
String language = Locale.getDefault().getLanguage();
if (TextUtils.isEmpty(language)) {
language = DEFAULT_LANGUAGE;
}
return language;
}
@NonNull
List<HistoryItem> getResults(@NonNull String query) {
List<HistoryItem> filter = new ArrayList<>(5); List<HistoryItem> filter = new ArrayList<>(5);
String query;
try { try {
query = URLEncoder.encode(query, mEncoding); query = URLEncoder.encode(rawQuery, mEncoding);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unable to encode the URL", e); Log.e(TAG, "Unable to encode the URL", e);
return filter;
} }
InputStream inputStream = downloadSuggestionsForQuery(query, mLanguage); InputStream inputStream = downloadSuggestionsForQuery(query, mLanguage);
if (inputStream == null) { if (inputStream == null) {
// There are no suggestions for this query, return an empty list. // There are no suggestions for this query, return an empty list.
@ -83,7 +104,6 @@ abstract class BaseSuggestionsModel {
parseResults(inputStream, filter); parseResults(inputStream, filter);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Unable to parse results", e); Log.e(TAG, "Unable to parse results", e);
return filter;
} finally { } finally {
Utils.close(inputStream); Utils.close(inputStream);
} }
@ -93,7 +113,7 @@ abstract class BaseSuggestionsModel {
/** /**
* This method downloads the search suggestions for the specific query. * This method downloads the search suggestions for the specific query.
* NOTE: This is a blocking operation, do not getResults on the UI thread. * NOTE: This is a blocking operation, do not fetchResults on the UI thread.
* *
* @param query the query to get suggestions for * @param query the query to get suggestions for
* @return the cache file containing the suggestions * @return the cache file containing the suggestions
@ -122,6 +142,16 @@ abstract class BaseSuggestionsModel {
return null; return null;
} }
@NonNull
private static String getLanguage() {
String language = Locale.getDefault().getLanguage();
if (TextUtils.isEmpty(language)) {
language = DEFAULT_LANGUAGE;
}
return language;
}
@NonNull
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
@Override @Override
public Response intercept(@NonNull Chain chain) throws IOException { public Response intercept(@NonNull Chain chain) throws IOException {

9
app/src/main/java/acr/browser/lightning/search/DuckSuggestionsModel.java → app/src/main/java/acr/browser/lightning/search/suggestions/DuckSuggestionsModel.java

@ -1,4 +1,4 @@
package acr.browser.lightning.search; package acr.browser.lightning.search.suggestions;
import android.app.Application; import android.app.Application;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -13,12 +13,15 @@ import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
final class DuckSuggestionsModel extends BaseSuggestionsModel { /**
* The search suggestions provider for the DuckDuckGo search engine.
*/
public final class DuckSuggestionsModel extends BaseSuggestionsModel {
@NonNull private static final String ENCODING = "UTF-8"; @NonNull private static final String ENCODING = "UTF-8";
@NonNull private final String mSearchSubtitle; @NonNull private final String mSearchSubtitle;
DuckSuggestionsModel(@NonNull Application application) { public DuckSuggestionsModel(@NonNull Application application) {
super(application, ENCODING); super(application, ENCODING);
mSearchSubtitle = application.getString(R.string.suggestion); mSearchSubtitle = application.getString(R.string.suggestion);
} }

13
app/src/main/java/acr/browser/lightning/search/GoogleSuggestionsModel.java → app/src/main/java/acr/browser/lightning/search/suggestions/GoogleSuggestionsModel.java

@ -1,4 +1,4 @@
package acr.browser.lightning.search; package acr.browser.lightning.search.suggestions;
import android.app.Application; import android.app.Application;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -15,20 +15,23 @@ import java.util.List;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
class GoogleSuggestionsModel extends BaseSuggestionsModel { /**
* Search suggestions provider for Google search engine.
*/
public class GoogleSuggestionsModel extends BaseSuggestionsModel {
@NonNull private static final String ENCODING = "ISO-8859-1"; @NonNull private static final String ENCODING = "UTF-8";
@Nullable private static XmlPullParser sXpp; @Nullable private static XmlPullParser sXpp;
@NonNull private final String mSearchSubtitle; @NonNull private final String mSearchSubtitle;
GoogleSuggestionsModel(@NonNull Application application) { public GoogleSuggestionsModel(@NonNull Application application) {
super(application, ENCODING); super(application, ENCODING);
mSearchSubtitle = application.getString(R.string.suggestion); mSearchSubtitle = application.getString(R.string.suggestion);
} }
@NonNull @NonNull
protected String createQueryUrl(@NonNull String query, @NonNull String language) { protected String createQueryUrl(@NonNull String query, @NonNull String language) {
return "https://suggestqueries.google.com/complete/search?output=toolbar&oe=latin1&hl=" return "https://suggestqueries.google.com/complete/search?output=toolbar&hl="
+ language + "&q=" + query; + language + "&q=" + query;
} }

20
app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java → app/src/main/java/acr/browser/lightning/settings/activity/SettingsActivity.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.activity; package acr.browser.lightning.settings.activity;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -19,17 +19,17 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
public class SettingsActivity extends ThemableSettingsActivity { public class SettingsActivity extends ThemableSettingsActivity {
@NonNull private static final List<String> mFragments = new ArrayList<>(7); @NonNull private static final List<String> sFragments = new ArrayList<>(7);
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// this is a workaround for the Toolbar in PreferenceActitivty // this is a workaround for the Toolbar in PreferenceActivity
ViewGroup root = (ViewGroup) findViewById(android.R.id.content); ViewGroup root = findViewById(android.R.id.content);
LinearLayout content = (LinearLayout) root.getChildAt(0); LinearLayout content = (LinearLayout) root.getChildAt(0);
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.toolbar_settings, null); LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.toolbar_settings, null);
@ -38,7 +38,7 @@ public class SettingsActivity extends ThemableSettingsActivity {
root.addView(toolbarContainer); root.addView(toolbarContainer);
// now we can set the Toolbar using AppCompatPreferenceActivity // now we can set the Toolbar using AppCompatPreferenceActivity
Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar); Toolbar toolbar = toolbarContainer.findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
@ -46,7 +46,7 @@ public class SettingsActivity extends ThemableSettingsActivity {
@Override @Override
public void onBuildHeaders(List<Header> target) { public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preferences_headers, target); loadHeadersFromResource(R.xml.preferences_headers, target);
mFragments.clear(); sFragments.clear();
Iterator<Header> headerIterator = target.iterator(); Iterator<Header> headerIterator = target.iterator();
while (headerIterator.hasNext()) { while (headerIterator.hasNext()) {
Header header = headerIterator.next(); Header header = headerIterator.next();
@ -59,17 +59,17 @@ public class SettingsActivity extends ThemableSettingsActivity {
if (BrowserApp.isRelease()) { if (BrowserApp.isRelease()) {
headerIterator.remove(); headerIterator.remove();
} else { } else {
mFragments.add(header.fragment); sFragments.add(header.fragment);
} }
} else { } else {
mFragments.add(header.fragment); sFragments.add(header.fragment);
} }
} }
} }
@Override @Override
protected boolean isValidFragment(String fragmentName) { protected boolean isValidFragment(String fragmentName) {
return mFragments.contains(fragmentName); return sFragments.contains(fragmentName);
} }
@Override @Override

5
app/src/main/java/acr/browser/lightning/activity/ThemableSettingsActivity.java → app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.settings.activity;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
@ -8,7 +8,8 @@ import android.os.Bundle;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.activity.AppCompatPreferenceActivity;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;

16
app/src/main/java/acr/browser/lightning/fragment/AboutSettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/AboutSettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
@ -9,13 +9,13 @@ import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.util.Log;
import acr.browser.lightning.R; import acr.browser.lightning.R;
public class AboutSettingsFragment extends PreferenceFragment { public class AboutSettingsFragment extends PreferenceFragment {
private Activity mActivity; private static final String TAG = "AboutSettingsFragment";
private static final String SETTINGS_VERSION = "pref_version"; private static final String SETTINGS_VERSION = "pref_version";
@Override @Override
@ -24,18 +24,18 @@ public class AboutSettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_about); addPreferencesFromResource(R.xml.preference_about);
mActivity = getActivity();
Preference version = findPreference(SETTINGS_VERSION); Preference version = findPreference(SETTINGS_VERSION);
version.setSummary(getVersion()); version.setSummary(getVersion());
} }
private String getVersion() { private String getVersion() {
try { try {
PackageInfo p = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(), 0); Activity activity = getActivity();
return p.versionName; String packageName = activity.getPackageName();
PackageInfo packageInfo = activity.getPackageManager().getPackageInfo(packageName, 0);
return packageInfo.versionName;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
e.printStackTrace(); Log.e(TAG, "getVersion: error", e);
return "1.0"; return "1.0";
} }
} }

2
app/src/main/java/acr/browser/lightning/fragment/AdvancedSettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;

12
app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
@ -23,6 +23,7 @@ import android.util.Log;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import com.anthonycr.bonsai.CompletableOnSubscribe; import com.anthonycr.bonsai.CompletableOnSubscribe;
import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.SingleOnSubscribe; import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.bonsai.Subscription; import com.anthonycr.bonsai.Subscription;
import com.anthonycr.grant.PermissionsManager; import com.anthonycr.grant.PermissionsManager;
@ -38,14 +39,11 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkExporter; import acr.browser.lightning.database.bookmark.BookmarkExporter;
import acr.browser.lightning.database.bookmark.BookmarkLocalSync; import acr.browser.lightning.database.bookmark.BookmarkLocalSync;
import acr.browser.lightning.database.bookmark.BookmarkLocalSync.Source; import acr.browser.lightning.database.bookmark.BookmarkLocalSync.Source;
import acr.browser.lightning.database.HistoryItem;
import com.anthonycr.bonsai.Schedulers;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
@ -495,7 +493,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
public void onComplete() { public void onComplete() {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity != null) { if (activity != null) {
String message = activity.getResources().getString(R.string.message_import); String message = activity.getString(R.string.message_import);
Utils.showSnackbar(activity, importList.size() + " " + message); Utils.showSnackbar(activity, importList.size() + " " + message);
} }
} }

4
app/src/main/java/acr/browser/lightning/fragment/DebugSettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/DebugSettingsFragment.java

@ -1,4 +1,4 @@
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
@ -10,7 +10,7 @@ import android.support.annotation.NonNull;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;

4
app/src/main/java/acr/browser/lightning/fragment/DisplaySettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
@ -152,7 +152,7 @@ public class DisplaySettingsFragment extends LightningPreferenceFragment impleme
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater(); LayoutInflater inflater = getActivity().getLayoutInflater();
LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_seek_bar, null); LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_seek_bar, null);
final SeekBar bar = (SeekBar) view.findViewById(R.id.text_size_seekbar); final SeekBar bar = view.findViewById(R.id.text_size_seekbar);
final TextView sample = new TextView(getActivity()); final TextView sample = new TextView(getActivity());
sample.setText(R.string.untitled); sample.setText(R.string.untitled);
sample.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)); sample.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT));

123
app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
@ -19,13 +19,22 @@ import android.text.InputFilter;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.webkit.URLUtil;
import android.widget.EditText; import android.widget.EditText;
import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.download.DownloadHandler; import acr.browser.lightning.search.SearchEngineProvider;
import acr.browser.lightning.search.engine.BaseSearchEngine;
import acr.browser.lightning.search.engine.CustomSearch;
import acr.browser.lightning.utils.FileUtils;
import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -53,12 +62,16 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
private int mAgentChoice; private int mAgentChoice;
private String mHomepage; private String mHomepage;
@Inject SearchEngineProvider mSearchEngineProvider;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_general); addPreferencesFromResource(R.xml.preference_general);
BrowserApp.getAppComponent().inject(this);
mActivity = getActivity(); mActivity = getActivity();
initPrefs(); initPrefs();
@ -106,7 +119,8 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
mPreferenceManager.setFlashSupport(0); mPreferenceManager.setFlashSupport(0);
} }
setSearchEngineSummary(mPreferenceManager.getSearchChoice()); BaseSearchEngine currentSearchEngine = mSearchEngineProvider.getCurrentSearchEngine();
setSearchEngineSummary(currentSearchEngine);
downloadloc.setSummary(mDownloadLocation); downloadloc.setSummary(mDownloadLocation);
@ -156,11 +170,15 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
cbAds.setEnabled(BuildConfig.FULL_VERSION); cbAds.setEnabled(BuildConfig.FULL_VERSION);
if (!BuildConfig.FULL_VERSION) {
cbAds.setSummary(R.string.upsell_plus_version);
}
if (API < Build.VERSION_CODES.KITKAT) { if (API < Build.VERSION_CODES.KITKAT) {
cbFlash.setEnabled(true); cbFlash.setEnabled(true);
} else { } else {
cbFlash.setEnabled(false); cbFlash.setEnabled(false);
cbFlash.setSummary(getResources().getString(R.string.flash_not_supported)); cbFlash.setSummary(R.string.flash_not_supported);
} }
cbImages.setChecked(imagesBool); cbImages.setChecked(imagesBool);
@ -170,18 +188,18 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
cbColorMode.setChecked(mPreferenceManager.getColorModeEnabled()); cbColorMode.setChecked(mPreferenceManager.getColorModeEnabled());
} }
private void searchUrlPicker() { private void showUrlPicker(@NonNull final CustomSearch customSearch) {
BrowserDialog.showEditText(mActivity, BrowserDialog.showEditText(mActivity,
R.string.custom_url, R.string.search_engine_custom,
R.string.custom_url, R.string.search_engine_custom,
mPreferenceManager.getSearchUrl(), mPreferenceManager.getSearchUrl(),
R.string.action_ok, R.string.action_ok,
new BrowserDialog.EditorListener() { new BrowserDialog.EditorListener() {
@Override @Override
public void onClick(String text) { public void onClick(String text) {
mPreferenceManager.setSearchUrl(text); mPreferenceManager.setSearchUrl(text);
searchengine.setSummary(mActivity.getString(R.string.custom_url) + ": " + text); setSearchEngineSummary(customSearch);
} }
}); });
@ -257,8 +275,8 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
private void manualProxyPicker() { private void manualProxyPicker() {
View v = mActivity.getLayoutInflater().inflate(R.layout.dialog_manual_proxy, null); View v = mActivity.getLayoutInflater().inflate(R.layout.dialog_manual_proxy, null);
final EditText eProxyHost = (EditText) v.findViewById(R.id.proxyHost); final EditText eProxyHost = v.findViewById(R.id.proxyHost);
final EditText eProxyPort = (EditText) v.findViewById(R.id.proxyPort); final EditText eProxyPort = v.findViewById(R.id.proxyPort);
// Limit the number of characters since the port needs to be of type int // Limit the number of characters since the port needs to be of type int
// Use input filters to limite the EditText length and determine the max // Use input filters to limite the EditText length and determine the max
@ -294,13 +312,24 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
BrowserDialog.setDialogSize(mActivity, dialog); BrowserDialog.setDialogSize(mActivity, dialog);
} }
@NonNull
private CharSequence[] convertSearchEngineToString(@NonNull List<BaseSearchEngine> searchEngines) {
CharSequence[] titles = new CharSequence[searchEngines.size()];
for (int n = 0; n < searchEngines.size(); n++) {
titles[n] = getString(searchEngines.get(n).getTitleRes());
}
return titles;
}
private void searchDialog() { private void searchDialog() {
AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); AlertDialog.Builder picker = new AlertDialog.Builder(mActivity);
picker.setTitle(getResources().getString(R.string.title_search_engine)); picker.setTitle(getResources().getString(R.string.title_search_engine));
CharSequence[] chars = {getResources().getString(R.string.custom_url), "Google",
"Ask", "Bing", "Yahoo", "StartPage", "StartPage (Mobile)", final List<BaseSearchEngine> searchEngineList = mSearchEngineProvider.getAllSearchEngines();
"DuckDuckGo (Privacy)", "DuckDuckGo Lite (Privacy)", "Baidu (Chinese)",
"Yandex (Russian)"}; CharSequence[] chars = convertSearchEngineToString(searchEngineList);
int n = mPreferenceManager.getSearchChoice(); int n = mPreferenceManager.getSearchChoice();
@ -308,8 +337,19 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
mPreferenceManager.setSearchChoice(which); BaseSearchEngine searchEngine = searchEngineList.get(which);
setSearchEngineSummary(which);
// Store the search engine preference
int preferencesIndex = mSearchEngineProvider.mapSearchEngineToPreferenceIndex(searchEngine);
mPreferenceManager.setSearchChoice(preferencesIndex);
if (searchEngine instanceof CustomSearch) {
// Show the URL picker
showUrlPicker((CustomSearch) searchEngine);
} else {
// Set the new search engine summary
setSearchEngineSummary(searchEngine);
}
} }
}); });
picker.setPositiveButton(R.string.action_ok, null); picker.setPositiveButton(R.string.action_ok, null);
@ -418,7 +458,7 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
private void homePicker() { private void homePicker() {
String currentHomepage; String currentHomepage;
mHomepage = mPreferenceManager.getHomepage(); mHomepage = mPreferenceManager.getHomepage();
if (!mHomepage.startsWith(Constants.ABOUT)) { if (!URLUtil.isAboutUrl(mHomepage)) {
currentHomepage = mHomepage; currentHomepage = mHomepage;
} else { } else {
currentHomepage = "https://www.google.com"; currentHomepage = "https://www.google.com";
@ -455,8 +495,8 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
switch (which) { switch (which) {
case 0: case 0:
mPreferenceManager.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH); mPreferenceManager.setDownloadDirectory(FileUtils.DEFAULT_DOWNLOAD_PATH);
downloadloc.setSummary(DownloadHandler.DEFAULT_DOWNLOAD_PATH); downloadloc.setSummary(FileUtils.DEFAULT_DOWNLOAD_PATH);
break; break;
case 1: case 1:
downPicker(); downPicker();
@ -519,7 +559,7 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
private void downPicker() { private void downPicker() {
View dialogView = LayoutInflater.from(mActivity).inflate(R.layout.dialog_edit_text, null); View dialogView = LayoutInflater.from(mActivity).inflate(R.layout.dialog_edit_text, null);
final EditText getDownload = (EditText) dialogView.findViewById(R.id.dialog_edit_text); final EditText getDownload = dialogView.findViewById(R.id.dialog_edit_text);
final int errorColor = ContextCompat.getColor(mActivity, R.color.error_red); final int errorColor = ContextCompat.getColor(mActivity, R.color.error_red);
final int regularColor = ThemeUtils.getTextColor(mActivity); final int regularColor = ThemeUtils.getTextColor(mActivity);
@ -535,7 +575,7 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
String text = getDownload.getText().toString(); String text = getDownload.getText().toString();
text = DownloadHandler.addNecessarySlashes(text); text = FileUtils.addNecessarySlashes(text);
mPreferenceManager.setDownloadDirectory(text); mPreferenceManager.setDownloadDirectory(text);
downloadloc.setSummary(text); downloadloc.setSummary(text);
} }
@ -544,40 +584,11 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
BrowserDialog.setDialogSize(mActivity, dialog); BrowserDialog.setDialogSize(mActivity, dialog);
} }
private void setSearchEngineSummary(int which) { private void setSearchEngineSummary(BaseSearchEngine baseSearchEngine) {
switch (which) { if (baseSearchEngine instanceof CustomSearch) {
case 0: searchengine.setSummary(mPreferenceManager.getSearchUrl());
searchUrlPicker(); } else {
break; searchengine.setSummary(getString(baseSearchEngine.getTitleRes()));
case 1:
searchengine.setSummary("Google");
break;
case 2:
searchengine.setSummary("Ask");
break;
case 3:
searchengine.setSummary("Bing");
break;
case 4:
searchengine.setSummary("Yahoo");
break;
case 5:
searchengine.setSummary("StartPage");
break;
case 6:
searchengine.setSummary("StartPage (Mobile)");
break;
case 7:
searchengine.setSummary("DuckDuckGo");
break;
case 8:
searchengine.setSummary("DuckDuckGo Lite");
break;
case 9:
searchengine.setSummary("Baidu");
break;
case 10:
searchengine.setSummary("Yandex");
} }
} }
@ -663,7 +674,7 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme
@Override @Override
public void afterTextChanged(@NonNull Editable s) { public void afterTextChanged(@NonNull Editable s) {
if (!DownloadHandler.isWriteAccessAvailable(s.toString())) { if (!FileUtils.isWriteAccessAvailable(s.toString())) {
this.getDownload.setTextColor(this.errorColor); this.getDownload.setTextColor(this.errorColor);
} else { } else {
this.getDownload.setTextColor(this.regularColor); this.getDownload.setTextColor(this.regularColor);

4
app/src/main/java/acr/browser/lightning/fragment/LightningPreferenceFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/LightningPreferenceFragment.java

@ -1,11 +1,11 @@
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
/** /**

12
app/src/main/java/acr/browser/lightning/fragment/PrivacySettingsFragment.java → app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.fragment; package acr.browser.lightning.settings.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
@ -20,8 +20,11 @@ import com.anthonycr.bonsai.CompletableOnSubscribe;
import com.anthonycr.bonsai.CompletableSubscriber; import com.anthonycr.bonsai.CompletableSubscriber;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
import acr.browser.lightning.utils.WebUtils; import acr.browser.lightning.utils.WebUtils;
@ -45,6 +48,8 @@ public class PrivacySettingsFragment extends LightningPreferenceFragment impleme
private Activity mActivity; private Activity mActivity;
@Inject HistoryModel mHistoryModel;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -187,7 +192,8 @@ public class PrivacySettingsFragment extends LightningPreferenceFragment impleme
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { public void onSubscribe(@NonNull CompletableSubscriber subscriber) {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity != null) { if (activity != null) {
WebUtils.clearHistory(activity); // TODO: 6/9/17 clearHistory is not synchronous
WebUtils.clearHistory(activity, mHistoryModel);
subscriber.onComplete(); subscriber.onComplete();
} }
subscriber.onError(new RuntimeException("Activity was null in clearHistory")); subscriber.onError(new RuntimeException("Activity was null in clearHistory"));

69
app/src/main/java/acr/browser/lightning/utils/DrawableUtils.java

@ -1,7 +1,9 @@
package acr.browser.lightning.utils; package acr.browser.lightning.utils;
import android.app.Application;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuffXfermode;
@ -9,12 +11,17 @@ import android.graphics.RectF;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.View; import android.view.View;
import acr.browser.lightning.R;
public class DrawableUtils { public class DrawableUtils {
@NonNull
public static Bitmap getRoundedNumberImage(int number, int width, int height, int color, int thickness) { public static Bitmap getRoundedNumberImage(int number, int width, int height, int color, int thickness) {
String text; String text;
@ -56,6 +63,68 @@ public class DrawableUtils {
return image; return image;
} }
/**
* Creates a rounded square of a certain color with
* a character imprinted in white on it.
*
* @param character the character to write on the image.
* @param width the width of the final image.
* @param height the height of the final image.
* @param color the background color of the rounded square.
* @return a valid bitmap of a rounded square with a character on it.
*/
@NonNull
public static Bitmap getRoundedLetterImage(@NonNull Character character, int width, int height, int color) {
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);
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
paint.setColor(Color.WHITE);
canvas.drawText(character.toString(), xPos, yPos, paint);
return image;
}
/**
* Hashes a character to one of four colors:
* blue, green, red, or orange.
*
* @param character the character to hash.
* @param app the application needed to get the color.
* @return one of the above colors, or black something goes wrong.
*/
@ColorInt
public static int characterToColorHash(@NonNull Character character, @NonNull Application app) {
int smallHash = Character.getNumericValue(character) % 4;
switch (Math.abs(smallHash)) {
case 0:
return ContextCompat.getColor(app, R.color.bookmark_default_blue);
case 1:
return ContextCompat.getColor(app, R.color.bookmark_default_green);
case 2:
return ContextCompat.getColor(app, R.color.bookmark_default_red);
case 3:
return ContextCompat.getColor(app, R.color.bookmark_default_orange);
default:
return Color.BLACK;
}
}
public static int mixColor(float fraction, int startValue, int endValue) { public static int mixColor(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff; int startA = (startValue >> 24) & 0xff;

86
app/src/main/java/acr/browser/lightning/utils/FileUtils.java

@ -28,6 +28,9 @@ public class FileUtils {
private static final String TAG = "FileUtils"; private static final String TAG = "FileUtils";
public static final String DEFAULT_DOWNLOAD_PATH =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
/** /**
* Writes a bundle to persistent storage in the files directory * Writes a bundle to persistent storage in the files directory
* using the specified file name. This method is a blocking * using the specified file name. This method is a blocking
@ -160,4 +163,87 @@ public class FileUtils {
return megaBytes * 1024 * 1024; return megaBytes * 1024 * 1024;
} }
/**
* Determine whether there is write access in the given directory. Returns false if a
* file cannot be created in the directory or if the directory does not exist.
*
* @param directory the directory to check for write access
* @return returns true if the directory can be written to or is in a directory that can
* be written to. false if there is no write access.
*/
public static boolean isWriteAccessAvailable(@Nullable String directory) {
if (directory == null || directory.isEmpty()) {
return false;
}
final String sFileName = "test";
final String sFileExtension = ".txt";
String dir = addNecessarySlashes(directory);
dir = getFirstRealParentDirectory(dir);
File file = new File(dir + sFileName + sFileExtension);
for (int n = 0; n < 100; n++) {
if (!file.exists()) {
try {
if (file.createNewFile()) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
return true;
} catch (IOException ignored) {
return false;
}
} else {
file = new File(dir + sFileName + '-' + n + sFileExtension);
}
}
return file.canWrite();
}
/**
* Returns the first parent directory of a directory that exists. This is useful
* for subdirectories that do not exist but their parents do.
*
* @param directory the directory to find the first existent parent
* @return the first existent parent
*/
@Nullable
private static String getFirstRealParentDirectory(@Nullable String directory) {
while (true) {
if (directory == null || directory.isEmpty()) {
return "/";
}
directory = addNecessarySlashes(directory);
File file = new File(directory);
if (!file.isDirectory()) {
int indexSlash = directory.lastIndexOf('/');
if (indexSlash > 0) {
String parent = directory.substring(0, indexSlash);
int previousIndex = parent.lastIndexOf('/');
if (previousIndex > 0) {
directory = parent.substring(0, previousIndex);
} else {
return "/";
}
} else {
return "/";
}
} else {
return directory;
}
}
}
@NonNull
public static String addNecessarySlashes(@Nullable String originalPath) {
if (originalPath == null || originalPath.length() == 0) {
return "/";
}
if (originalPath.charAt(originalPath.length() - 1) != '/') {
originalPath = originalPath + '/';
}
if (originalPath.charAt(0) != '/') {
originalPath = '/' + originalPath;
}
return originalPath;
}
} }

2
app/src/main/java/acr/browser/lightning/utils/IntentUtils.java

@ -1,14 +1,12 @@
package acr.browser.lightning.utils; package acr.browser.lightning.utils;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.FileUriExposedException;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;

2
app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java

@ -13,7 +13,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;

12
app/src/main/java/acr/browser/lightning/utils/ResourceUtils.java

@ -4,11 +4,21 @@ import android.content.Context;
import android.support.annotation.DimenRes; import android.support.annotation.DimenRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
/**
* Utils related to resources.
*/
public final class ResourceUtils { public final class ResourceUtils {
private ResourceUtils() {} private ResourceUtils() {}
/**
* Returns the dimension in pixels.
*
* @param context the context needed to get the dimension.
* @param res the resource to get.
* @return the dimension value in pixels.
*/
public static int dimen(@NonNull Context context, @DimenRes int res) { public static int dimen(@NonNull Context context, @DimenRes int res) {
return Math.round(context.getResources().getDimension(res)); return context.getResources().getDimensionPixelSize(res);
} }
} }

10
app/src/main/java/acr/browser/lightning/utils/StringBuilderUtils.java

@ -14,16 +14,16 @@ public class StringBuilderUtils {
private static final String EMPTY = ""; private static final String EMPTY = "";
/** /**
* Replace a string in a string * Replace the first string found in a
* builder with another string. * string builder with another string.
* *
* @param stringBuilder the string builder. * @param stringBuilder the string builder.
* @param toReplace the string to replace. * @param toReplace the string to replace.
* @param replacement the replacement string. * @param replacement the replacement string.
*/ */
public static void replace(@NonNull StringBuilder stringBuilder, public static void replace(@NonNull StringBuilder stringBuilder,
@NonNull String toReplace, @NonNull String toReplace,
@NonNull String replacement) { @NonNull String replacement) {
int index = stringBuilder.indexOf(toReplace); int index = stringBuilder.indexOf(toReplace);
if (index >= 0) { if (index >= 0) {
stringBuilder.replace(index, index + toReplace.length(), replacement); stringBuilder.replace(index, index + toReplace.length(), replacement);
@ -42,7 +42,7 @@ public class StringBuilderUtils {
stringBuilder.replace(0, 1, EMPTY); stringBuilder.replace(0, 1, EMPTY);
} }
while (stringBuilder.lastIndexOf(SPACE) == (stringBuilder.length() - 1)) { while (stringBuilder.lastIndexOf(SPACE) == (stringBuilder.length() - 1) && stringBuilder.length() > 0) {
stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), EMPTY); stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), EMPTY);
} }
} }

39
app/src/main/java/acr/browser/lightning/utils/Utils.java

@ -3,7 +3,6 @@
*/ */
package acr.browser.lightning.utils; package acr.browser.lightning.utils;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -36,9 +35,6 @@ import android.view.View;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import android.widget.Toast; import android.widget.Toast;
import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -48,12 +44,10 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.MainActivity; import acr.browser.lightning.MainActivity;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.download.DownloadHandler;
import acr.browser.lightning.preference.PreferenceManager;
public final class Utils { public final class Utils {
@ -63,35 +57,6 @@ public final class Utils {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 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(@NonNull final Activity activity, @NonNull 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(TAG, "Downloading: " + fileName);
}
@Override
public void onDenied(String permission) {
// TODO Show Message
}
});
}
/** /**
* Creates a new intent that can launch the email * Creates a new intent that can launch the email
* app with a subject, address, body, and cc. It * app with a subject, address, body, and cc. It
@ -211,7 +176,7 @@ public final class Utils {
public static String getDomainName(@Nullable String url) { public static String getDomainName(@Nullable String url) {
if (url == null || url.isEmpty()) return ""; if (url == null || url.isEmpty()) return "";
boolean ssl = url.startsWith(Constants.HTTPS); boolean ssl = URLUtil.isHttpsUrl(url);
int index = url.indexOf('/', 8); int index = url.indexOf('/', 8);
if (index != -1) { if (index != -1) {
url = url.substring(0, index); url = url.substring(0, index);

4
app/src/main/java/acr/browser/lightning/utils/WebUtils.java

@ -36,8 +36,8 @@ public class WebUtils {
WebStorage.getInstance().deleteAllData(); WebStorage.getInstance().deleteAllData();
} }
public static void clearHistory(@NonNull Context context) { public static void clearHistory(@NonNull Context context, @NonNull HistoryModel historyModel) {
HistoryModel.deleteHistory() historyModel.deleteHistory()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(); .subscribe();
WebViewDatabase m = WebViewDatabase.getInstance(context); WebViewDatabase m = WebViewDatabase.getInstance(context);

42
app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java

@ -25,7 +25,7 @@ import com.anthonycr.grant.PermissionsResultAction;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.favicon.FaviconModel; import acr.browser.lightning.favicon.FaviconModel;
@ -73,15 +73,9 @@ public class LightningChromeClient extends WebChromeClient {
return; return;
} }
Uri uri = Uri.parse(url);
if (uri.getHost() == null) {
return;
}
mFaviconModel.cacheFaviconForUrl(icon, url) mFaviconModel.cacheFaviconForUrl(icon, url)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(); .subscribe();
} }
@ -114,21 +108,21 @@ public class LightningChromeClient extends WebChromeClient {
org = origin; org = origin;
} }
builder.setMessage(org + mActivity.getString(R.string.message_location)) builder.setMessage(org + mActivity.getString(R.string.message_location))
.setCancelable(true) .setCancelable(true)
.setPositiveButton(mActivity.getString(R.string.action_allow), .setPositiveButton(mActivity.getString(R.string.action_allow),
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, true, remember); callback.invoke(origin, true, remember);
} }
}) })
.setNegativeButton(mActivity.getString(R.string.action_dont_allow), .setNegativeButton(mActivity.getString(R.string.action_dont_allow),
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, false, remember); callback.invoke(origin, false, remember);
} }
}); });
AlertDialog alert = builder.create(); AlertDialog alert = builder.create();
alert.show(); alert.show();
BrowserDialog.setDialogSize(mActivity, alert); BrowserDialog.setDialogSize(mActivity, alert);

51
app/src/main/java/acr/browser/lightning/view/LightningView.java

@ -44,11 +44,10 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.constant.DownloadsPage; import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
@ -106,7 +105,7 @@ public class LightningView {
@NonNull private final Map<String, String> mRequestHeaders = new ArrayMap<>(); @NonNull private final Map<String, String> mRequestHeaders = new ArrayMap<>();
@Inject PreferenceManager mPreferences; @Inject PreferenceManager mPreferences;
@Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject LightningDialogBuilder mDialogBuilder;
@Inject ProxyUtils mProxyUtils; @Inject ProxyUtils mProxyUtils;
public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) { public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) {
@ -250,15 +249,15 @@ public class LightningView {
*/ */
public void loadDownloadspage() { public void loadDownloadspage() {
new DownloadsPage().getDownloadsPage() new DownloadsPage().getDownloadsPage()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<String>() { .subscribe(new SingleOnSubscribe<String>() {
@Override @Override
public void onItem(@Nullable String item) { public void onItem(@Nullable String item) {
Preconditions.checkNonNull(item); Preconditions.checkNonNull(item);
loadUrl(item); loadUrl(item);
} }
}); });
} }
/** /**
@ -989,45 +988,45 @@ public class LightningView {
final WebView.HitTestResult result = mWebView.getHitTestResult(); final WebView.HitTestResult result = mWebView.getHitTestResult();
String currentUrl = mWebView.getUrl(); String currentUrl = mWebView.getUrl();
if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) { if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) {
if (currentUrl.endsWith(HistoryPage.FILENAME)) { if (UrlUtils.isHistoryUrl(currentUrl)) {
if (url != null) { if (url != null) {
mBookmarksDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, mUIController, url); mDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, mUIController, url);
} else if (result != null && result.getExtra() != null) { } else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
mBookmarksDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, mUIController, newUrl); mDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, mUIController, newUrl);
} }
} else if (currentUrl.endsWith(BookmarkPage.FILENAME)) { } else if (UrlUtils.isBookmarkUrl(currentUrl)) {
if (url != null) { if (url != null) {
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, mUIController, url); mDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, mUIController, url);
} else if (result != null && result.getExtra() != null) { } else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, mUIController, newUrl); mDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, mUIController, newUrl);
} }
} else if (currentUrl.endsWith(DownloadsPage.FILENAME)) { } else if (UrlUtils.isDownloadsUrl(currentUrl)) {
if (url != null) { if (url != null) {
mBookmarksDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, url); mDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, url);
} else if (result != null && result.getExtra() != null) { } else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
mBookmarksDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, newUrl); mDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, newUrl);
} }
} }
} else { } else {
if (url != null) { if (url != null) {
if (result != null) { if (result != null) {
if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) { if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) {
mBookmarksDialogBuilder.showLongPressImageDialog(mActivity, mUIController, url, getUserAgent()); mDialogBuilder.showLongPressImageDialog(mActivity, mUIController, url, getUserAgent());
} else { } else {
mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, url); mDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, url);
} }
} else { } else {
mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, url); mDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, url);
} }
} else if (result != null && result.getExtra() != null) { } else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) { if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) {
mBookmarksDialogBuilder.showLongPressImageDialog(mActivity, mUIController, newUrl, getUserAgent()); mDialogBuilder.showLongPressImageDialog(mActivity, mUIController, newUrl, getUserAgent());
} else { } else {
mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, newUrl); mDialogBuilder.showLongPressLinkDialog(mActivity, mUIController, newUrl);
} }
} }
} }

29
app/src/main/java/acr/browser/lightning/view/LightningWebClient.java

@ -22,6 +22,7 @@ import android.view.View;
import android.webkit.HttpAuthHandler; import android.webkit.HttpAuthHandler;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.webkit.SslErrorHandler; import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse; import android.webkit.WebResourceResponse;
@ -41,14 +42,15 @@ import javax.inject.Inject;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.utils.AdBlock; import acr.browser.lightning.adblock.AdBlock;
import acr.browser.lightning.utils.IntentUtils; import acr.browser.lightning.utils.IntentUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.utils.UrlUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
public class LightningWebClient extends WebViewClient { public class LightningWebClient extends WebViewClient {
@ -100,7 +102,7 @@ public class LightningWebClient extends WebViewClient {
@Override @Override
public void onPageFinished(@NonNull WebView view, String url) { public void onPageFinished(@NonNull WebView view, String url) {
if (view.isShown()) { if (view.isShown()) {
mUIController.updateUrl(url, true); mUIController.updateUrl(url, false);
mUIController.setBackButtonEnabled(view.canGoBack()); mUIController.setBackButtonEnabled(view.canGoBack());
mUIController.setForwardButtonEnabled(view.canGoForward()); mUIController.setForwardButtonEnabled(view.canGoForward());
view.postInvalidate(); view.postInvalidate();
@ -121,7 +123,7 @@ public class LightningWebClient extends WebViewClient {
public void onPageStarted(WebView view, String url, Bitmap favicon) { public void onPageStarted(WebView view, String url, Bitmap favicon) {
mLightningView.getTitleInfo().setFavicon(null); mLightningView.getTitleInfo().setFavicon(null);
if (mLightningView.isShown()) { if (mLightningView.isShown()) {
mUIController.updateUrl(url, false); mUIController.updateUrl(url, true);
mUIController.showActionBar(); mUIController.showActionBar();
} }
mUIController.tabChanged(mLightningView); mUIController.tabChanged(mLightningView);
@ -135,9 +137,9 @@ public class LightningWebClient extends WebViewClient {
View dialogView = LayoutInflater.from(mActivity).inflate(R.layout.dialog_auth_request, null); View dialogView = LayoutInflater.from(mActivity).inflate(R.layout.dialog_auth_request, null);
final TextView realmLabel = (TextView) dialogView.findViewById(R.id.auth_request_realm_textview); final TextView realmLabel = dialogView.findViewById(R.id.auth_request_realm_textview);
final EditText name = (EditText) dialogView.findViewById(R.id.auth_request_username_edittext); final EditText name = dialogView.findViewById(R.id.auth_request_username_edittext);
final EditText password = (EditText) dialogView.findViewById(R.id.auth_request_password_edittext); final EditText password = dialogView.findViewById(R.id.auth_request_password_edittext);
realmLabel.setText(mActivity.getString(R.string.label_realm, realm)); realmLabel.setText(mActivity.getString(R.string.label_realm, realm));
@ -304,7 +306,7 @@ public class LightningWebClient extends WebViewClient {
// If we are in incognito, immediately load, we don't want the url to leave the app // If we are in incognito, immediately load, we don't want the url to leave the app
return continueLoadingUrl(view, url, headers); return continueLoadingUrl(view, url, headers);
} }
if (url.startsWith(Constants.ABOUT)) { if (URLUtil.isAboutUrl(url)) {
// If this is an about page, immediately load, we don't need to leave the app // If this is an about page, immediately load, we don't need to leave the app
return continueLoadingUrl(view, url, headers); return continueLoadingUrl(view, url, headers);
} }
@ -359,15 +361,14 @@ public class LightningWebClient extends WebViewClient {
} }
return true; return true;
} }
} else if (url.startsWith(Constants.FILE)) { } else if (URLUtil.isFileUrl(url) && !UrlUtils.isSpecialUrl(url)) {
File file = new File(url.replace(Constants.FILE, "")); File file = new File(url.replace(Constants.FILE, ""));
if (file.exists()) { if (file.exists()) {
String newMimeType = MimeTypeMap.getSingleton() String newMimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(Utils.guessFileExtension(file.toString())); .getMimeTypeFromExtension(Utils.guessFileExtension(file.toString()));
Intent intent = new Intent(); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(mActivity, BuildConfig.APPLICATION_ID + ".fileprovider", file); Uri contentUri = FileProvider.getUriForFile(mActivity, BuildConfig.APPLICATION_ID + ".fileprovider", file);
intent.setDataAndType(contentUri, newMimeType); intent.setDataAndType(contentUri, newMimeType);
@ -377,8 +378,10 @@ public class LightningWebClient extends WebViewClient {
} catch (Exception e) { } catch (Exception e) {
System.out.println("LightningWebClient: cannot open downloaded file"); System.out.println("LightningWebClient: cannot open downloaded file");
} }
return true; } else {
Utils.showSnackbar(mActivity, R.string.message_open_download_fail);
} }
return true;
} }
return false; return false;
} }

2
app/src/main/res/layout/activity_main.xml

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".activity.BrowserActivity"> tools:context=".browser.activity.BrowserActivity">
<android.support.v4.widget.DrawerLayout <android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"

4
app/src/main/res/layout/bookmark_list_item.xml

@ -9,8 +9,8 @@
<ImageView <ImageView
android:id="@+id/faviconBookmark" android:id="@+id/faviconBookmark"
android:layout_width="24dp" android:layout_width="@dimen/bookmark_item_icon_size"
android:layout_height="24dp" android:layout_height="@dimen/bookmark_item_icon_size"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:contentDescription="Favicon" android:contentDescription="Favicon"

8
app/src/main/res/raw/default_bookmarks.dat

@ -1,5 +1,5 @@
{"url": "https://twitter.com/RestainoAnthony", "title": "Contact Me", "folder": "", "order": 0} {"url": "https://twitter.com/RestainoAnthony", "title": "Contact Me", "folder": "", "order": 0}
{"url": "https://www.facebook.com/", "title": "Facebook", "folder": "", "order": 2} {"url": "https://www.facebook.com/", "title": "Facebook", "folder": "", "order": 1}
{"url": "https://twitter.com", "title": "Twitter", "folder": "", "order": 3} {"url": "https://twitter.com/", "title": "Twitter", "folder": "", "order": 2}
{"url": "https://www.google.com/", "title": "Google", "folder": "", "order": 4} {"url": "https://www.google.com/", "title": "Google", "folder": "", "order": 3}
{"url": "https://www.yahoo.com/", "title": "Yahoo", "folder": "", "order": 5} {"url": "https://www.wikipedia.org/", "title": "Wikipedia", "folder": "", "order": 4}

5
app/src/main/res/values-de/strings.xml

@ -119,7 +119,7 @@
<string name="max_tabs">Maximale Reiteranzahl erreicht!</string> <string name="max_tabs">Maximale Reiteranzahl erreicht!</string>
<string name="message_text_copied">Text in Zwischenablage kopiert</string> <string name="message_text_copied">Text in Zwischenablage kopiert</string>
<string name="message_link_copied">Link in Zwischenablage kopiert</string> <string name="message_link_copied">Link in Zwischenablage kopiert</string>
<string name="custom_url">Benutzerdefinierte URL</string> <string name="search_engine_custom">Benutzerdefinierte URL</string>
<string name="message_blocked_local">Lokale Datei wurde beim Laden blockiert</string> <string name="message_blocked_local">Lokale Datei wurde beim Laden blockiert</string>
<string name="licenses">Open Source-Lizenzen</string> <string name="licenses">Open Source-Lizenzen</string>
<string name="suggestion">Suche nach</string> <string name="suggestion">Suche nach</string>
@ -160,7 +160,7 @@
<string name="export_bookmarks">Exportiere Datensicherung</string> <string name="export_bookmarks">Exportiere Datensicherung</string>
<string name="import_backup">Importiere Lesezeichen aus Datensicherung</string> <string name="import_backup">Importiere Lesezeichen aus Datensicherung</string>
<string name="bookmark_export_path">Lesezeichen exportiert nach</string> <string name="bookmark_export_path">Lesezeichen exportiert nach</string>
<string name="bookmark_settings">Lesezeichen Einstellungen</string> <string name="bookmark_settings">Lesezeichen</string>
<string name="import_bookmark_error">Lesezeichen konnten nicht importiert werden</string> <string name="import_bookmark_error">Lesezeichen konnten nicht importiert werden</string>
<string name="title_chooser">Wähle Datei aus</string> <string name="title_chooser">Wähle Datei aus</string>
<string name="settings_general">Allgemein</string> <string name="settings_general">Allgemein</string>
@ -246,4 +246,5 @@
<string name="host">Host:</string> <string name="host">Host:</string>
<string name="hosts_source">Quelle für Werbeblocker</string> <string name="hosts_source">Quelle für Werbeblocker</string>
<string name="settings_display">Anzeige</string> <string name="settings_display">Anzeige</string>
<string name="message_open_download_fail">Diese Datei existiert nicht mehr</string>
</resources> </resources>

2
app/src/main/res/values-el/strings.xml

@ -103,7 +103,7 @@
<string name="max_tabs">Μέγιστος αριθμός καρτελών ξεπεράστηκε</string> <string name="max_tabs">Μέγιστος αριθμός καρτελών ξεπεράστηκε</string>
<string name="message_text_copied">Το κείμενο αντιγράφτηκε</string> <string name="message_text_copied">Το κείμενο αντιγράφτηκε</string>
<string name="message_link_copied">Ο σύνδεσμος αντιγράφτηκε</string> <string name="message_link_copied">Ο σύνδεσμος αντιγράφτηκε</string>
<string name="custom_url">Ειδικό URL</string> <string name="search_engine_custom">Ειδικό URL</string>
<string name="message_blocked_local">Τοπικά αρχεία εχουν μπλοκαριστεί απο την φόρτωση</string> <string name="message_blocked_local">Τοπικά αρχεία εχουν μπλοκαριστεί απο την φόρτωση</string>
<string name="licenses">Άδειες ανοιχτού κώδικα</string> <string name="licenses">Άδειες ανοιχτού κώδικα</string>
<string name="suggestion">Αναζήτηση για</string> <string name="suggestion">Αναζήτηση για</string>

2
app/src/main/res/values-es/strings.xml

@ -103,7 +103,7 @@
<string name="max_tabs">Se ha alcanzado el máximo de pestañas</string> <string name="max_tabs">Se ha alcanzado el máximo de pestañas</string>
<string name="message_text_copied">El texto se ha copiado al portapapeles</string> <string name="message_text_copied">El texto se ha copiado al portapapeles</string>
<string name="message_link_copied">El enlace se ha copiado al portapapeles</string> <string name="message_link_copied">El enlace se ha copiado al portapapeles</string>
<string name="custom_url">Dirección personalizada</string> <string name="search_engine_custom">Dirección personalizada</string>
<string name="message_blocked_local">Se ha bloqueado la carga del archivo local</string> <string name="message_blocked_local">Se ha bloqueado la carga del archivo local</string>
<string name="licenses">Licencias de código abierto (open source)</string> <string name="licenses">Licencias de código abierto (open source)</string>
<string name="suggestion">Buscar</string> <string name="suggestion">Buscar</string>

2
app/src/main/res/values-fr/strings.xml

@ -125,7 +125,7 @@
<string name="max_tabs">Onglets maximuns atteint</string> <string name="max_tabs">Onglets maximuns atteint</string>
<string name="message_text_copied">Texte copié dans le presse-papiers</string> <string name="message_text_copied">Texte copié dans le presse-papiers</string>
<string name="message_link_copied">Adresse copiée dans le presse-papiers</string> <string name="message_link_copied">Adresse copiée dans le presse-papiers</string>
<string name="custom_url">Adresse personnalisée</string> <string name="search_engine_custom">Adresse personnalisée</string>
<string name="message_blocked_local">Le fichier local a été bloqué au chargement</string> <string name="message_blocked_local">Le fichier local a été bloqué au chargement</string>
<string name="licenses">Licences open source</string> <string name="licenses">Licences open source</string>
<string name="suggestion">Recherche</string> <string name="suggestion">Recherche</string>

2
app/src/main/res/values-hu/strings.xml

@ -124,7 +124,7 @@
<string name="max_tabs">Maximálisan megnyitható lapok száma elérve</string> <string name="max_tabs">Maximálisan megnyitható lapok száma elérve</string>
<string name="message_text_copied">Szöveg másolva a vágólapra</string> <string name="message_text_copied">Szöveg másolva a vágólapra</string>
<string name="message_link_copied">Link másolva a vágólapra</string> <string name="message_link_copied">Link másolva a vágólapra</string>
<string name="custom_url">Egyéni URL</string> <string name="search_engine_custom">Egyéni URL</string>
<string name="message_blocked_local">Helyi fájl betöltése blokkolva</string> <string name="message_blocked_local">Helyi fájl betöltése blokkolva</string>
<string name="licenses">Nyílt forráskódú licencek</string> <string name="licenses">Nyílt forráskódú licencek</string>
<string name="suggestion">Keresés</string> <string name="suggestion">Keresés</string>

4
app/src/main/res/values-it/strings.xml

@ -124,7 +124,7 @@
<string name="max_tabs">Hai raggiunto il numero massimo di schede apribili</string> <string name="max_tabs">Hai raggiunto il numero massimo di schede apribili</string>
<string name="message_text_copied">Testo copiato negli appunti</string> <string name="message_text_copied">Testo copiato negli appunti</string>
<string name="message_link_copied">Link copiato negli appunti</string> <string name="message_link_copied">Link copiato negli appunti</string>
<string name="custom_url">URL personalizzato</string> <string name="search_engine_custom">URL personalizzato</string>
<string name="message_blocked_local">Caricamento file locale bloccato</string> <string name="message_blocked_local">Caricamento file locale bloccato</string>
<string name="licenses">Licenze Open Source</string> <string name="licenses">Licenze Open Source</string>
<string name="suggestion">Cerca</string> <string name="suggestion">Cerca</string>
@ -237,7 +237,7 @@
<string name="message_added_to_homescreen">Scorciatoia aggiunta</string> <string name="message_added_to_homescreen">Scorciatoia aggiunta</string>
<string name="action_delete_all_bookmarks">Cancella tutti i segnalibri</string> <string name="action_delete_all_bookmarks">Cancella tutti i segnalibri</string>
<string name="swap_bookmarks_and_tabs">Inverti pannello segnalibri con schede</string> <string name="swap_bookmarks_and_tabs">Inverti pannello segnalibri con schede</string>
<string name="flash_not_supported">Flash Player non è supportato dal tuo sistema!</string> <string name="flash_not_supported">Flash Player non è supportato dal tuo sistema</string>
<string name="faq">FAQ</string> <string name="faq">FAQ</string>
<string name="faq_description">Domande più frequenti</string> <string name="faq_description">Domande più frequenti</string>

28
app/src/main/res/values-ja/strings.xml

@ -1,24 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
Copyright 2013 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.
-->
<resources>
<string name="app_name">Lightning</string> <string name="app_name">Lightning</string>
<string name="action_new_tab">新しいタブ</string> <string name="action_new_tab">新しいタブ</string>
<string name="action_share">共有</string> <string name="action_share">共有</string>
<string name="action_history">履歴</string> <string name="action_history">履歴</string>
<string name="action_bookmarks">ブックマーク</string> <string name="action_bookmarks">ブックマーク</string>
<string name="action_downloads">ダウンロード</string>
<string name="action_add_bookmark">ブックマークに追加</string> <string name="action_add_bookmark">ブックマークに追加</string>
<string name="action_copy">ページの URL をコピー</string> <string name="action_copy">ページの URL をコピー</string>
<string name="action_forward">進む</string> <string name="action_forward">進む</string>
@ -70,6 +58,7 @@
<string name="title_search_engine">検索エンジン</string> <string name="title_search_engine">検索エンジン</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="dialog_download">このファイルをダウンロードしますか? (%1$s)</string> <string name="dialog_download">このファイルをダウンロードしますか? (%1$s)</string>
<string name="unknown_size">サイズ不明</string>
<string name="action_cancel">キャンセル</string> <string name="action_cancel">キャンセル</string>
<string name="title_warning">警告</string> <string name="title_warning">警告</string>
<string name="dialog_adobe_not_installed">Adobe Flash Player が見つかりません\nFlash Player をインストールしてください</string> <string name="dialog_adobe_not_installed">Adobe Flash Player が見つかりません\nFlash Player をインストールしてください</string>
@ -121,7 +110,7 @@
<string name="max_tabs">これ以上タブは開けません</string> <string name="max_tabs">これ以上タブは開けません</string>
<string name="message_text_copied">テキストをクリップボードにコピーしました</string> <string name="message_text_copied">テキストをクリップボードにコピーしました</string>
<string name="message_link_copied">リンクをクリップボードにコピーしました</string> <string name="message_link_copied">リンクをクリップボードにコピーしました</string>
<string name="custom_url">カスタム URL</string> <string name="search_engine_custom">カスタム URL</string>
<string name="message_blocked_local">ファイルを読み込めませんでした</string> <string name="message_blocked_local">ファイルを読み込めませんでした</string>
<string name="licenses">オープンソース ライセンス</string> <string name="licenses">オープンソース ライセンス</string>
<string name="suggestion">検索 :</string> <string name="suggestion">検索 :</string>
@ -139,11 +128,13 @@
<string name="action_allow">許可する</string> <string name="action_allow">許可する</string>
<string name="action_dont_allow">許可しない</string> <string name="action_dont_allow">許可しない</string>
<string name="title_sign_in">サインイン</string> <string name="title_sign_in">サインイン</string>
<string name="label_realm">サーバー メッセージ: %s</string>
<string name="hint_username">ユーザー名</string> <string name="hint_username">ユーザー名</string>
<string name="hint_password">パスワード</string> <string name="hint_password">パスワード</string>
<string name="search_suggestions">検索候補表示</string> <string name="search_suggestions">検索候補表示</string>
<string name="powered_by_google">Google を使用する</string> <string name="powered_by_google">Google を使用する</string>
<string name="powered_by_duck">DuckDuckGo を使用する</string> <string name="powered_by_duck">DuckDuckGo を使用する</string>
<string name="powered_by_baidu">Baidu を使用する</string>
<string name="search_suggestions_off">検索候補を表示しない</string> <string name="search_suggestions_off">検索候補を表示しない</string>
<string name="http_proxy">HTTP プロキシ</string> <string name="http_proxy">HTTP プロキシ</string>
<string-array name="proxy_choices_array"> <string-array name="proxy_choices_array">
@ -185,6 +176,7 @@
<string name="export_bookmarks">ブックマークをエクスポートする</string> <string name="export_bookmarks">ブックマークをエクスポートする</string>
<string name="import_backup">ブックマークをインポートする</string> <string name="import_backup">ブックマークをインポートする</string>
<string name="bookmark_export_path">ブックマークをエクスポートしました</string> <string name="bookmark_export_path">ブックマークをエクスポートしました</string>
<string name="bookmark_export_failure">ブックマークのエクスポートに失敗しました!</string>
<string name="bookmark_settings">ブックマーク</string> <string name="bookmark_settings">ブックマーク</string>
<string name="import_bookmark_error">ブックマークのインポートに失敗しました</string> <string name="import_bookmark_error">ブックマークのインポートに失敗しました</string>
<string name="title_chooser">ファイルを選択してください</string> <string name="title_chooser">ファイルを選択してください</string>
@ -213,6 +205,7 @@
</string-array> </string-array>
<string name="invert_color">色を反転する</string> <string name="invert_color">色を反転する</string>
<string name="dark_theme">ダーク</string> <string name="dark_theme">ダーク</string>
<string name="settings_black_status_bar">ステータスバーを暗色にする</string>
<string name="tabs">タブ</string> <string name="tabs">タブ</string>
<string name="theme">アプリのテーマ</string> <string name="theme">アプリのテーマ</string>
<string name="light_theme">ライト</string> <string name="light_theme">ライト</string>
@ -234,6 +227,9 @@
<string name="action_add_to_homescreen">ホーム画面に追加</string> <string name="action_add_to_homescreen">ホーム画面に追加</string>
<string name="message_added_to_homescreen">ホーム画面にショートカットを追加しました</string> <string name="message_added_to_homescreen">ホーム画面にショートカットを追加しました</string>
<string name="action_delete_all_bookmarks">すべてのブックマークを削除する</string> <string name="action_delete_all_bookmarks">すべてのブックマークを削除する</string>
<string name="flash_not_supported">このシステムは Flash Player をサポートしていません</string>
<string name="upsell_plus_version">広告をブロックするには Lightning Plus へアップグレードしてください</string>
<string name="message_open_download_fail">このファイルは存在しません</string>
<string name="faq">FAQ</string> <string name="faq">FAQ</string>
<string name="faq_description">よくある質問と回答</string> <string name="faq_description">よくある質問と回答</string>
@ -255,4 +251,6 @@
<string name="dialog_rename_folder">フォルダ名を変更する</string> <string name="dialog_rename_folder">フォルダ名を変更する</string>
<string name="dialog_remove_folder">フォルダを削除する</string> <string name="dialog_remove_folder">フォルダを削除する</string>
<string name="dialog_title_close_browser">タブの消去</string> <string name="dialog_title_close_browser">タブの消去</string>
<string name="dialog_delete_download">ダウンロードの削除</string>
<string name="dialog_delete_all_downloads">ダウンロードの全削除</string>
</resources> </resources>

2
app/src/main/res/values-ko/strings.xml

@ -102,7 +102,7 @@
<string name="max_tabs">최대 탭 수에 도달했습니다</string> <string name="max_tabs">최대 탭 수에 도달했습니다</string>
<string name="message_text_copied">텍스트가 클립보드로 복사되었습니다</string> <string name="message_text_copied">텍스트가 클립보드로 복사되었습니다</string>
<string name="message_link_copied">링크가 클립보드로 복사되었습니다</string> <string name="message_link_copied">링크가 클립보드로 복사되었습니다</string>
<string name="custom_url">임의의 URL</string> <string name="search_engine_custom">임의의 URL</string>
<string name="message_blocked_local">로컬 파일 불러오기가 차단되었습니다</string> <string name="message_blocked_local">로컬 파일 불러오기가 차단되었습니다</string>
<string name="licenses">오픈 소스 라이센스</string> <string name="licenses">오픈 소스 라이센스</string>
<string name="suggestion">다음을 검색</string> <string name="suggestion">다음을 검색</string>

2
app/src/main/res/values-nl/strings.xml

@ -124,7 +124,7 @@
<string name="max_tabs">Maximum aantal tabbladen bereikt</string> <string name="max_tabs">Maximum aantal tabbladen bereikt</string>
<string name="message_text_copied">Tekst gekopieerd naar klembord</string> <string name="message_text_copied">Tekst gekopieerd naar klembord</string>
<string name="message_link_copied">Link gekopieerd naar klembord</string> <string name="message_link_copied">Link gekopieerd naar klembord</string>
<string name="custom_url">Aangepaste URL</string> <string name="search_engine_custom">Aangepaste URL</string>
<string name="message_blocked_local">Lokaal bestand werd geblokkeerd</string> <string name="message_blocked_local">Lokaal bestand werd geblokkeerd</string>
<string name="licenses">Open Source Licenses</string> <string name="licenses">Open Source Licenses</string>
<string name="suggestion">Zoek voor</string> <string name="suggestion">Zoek voor</string>

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

Loading…
Cancel
Save