From 0654983dcc5b0ea6e0fec5fcf70bb28bcd56ff1b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 9 Sep 2019 17:45:23 -0300 Subject: [PATCH 01/26] Removing unnecessary padding and setting contentDescription --- app/src/main/res/layout/list_dialog.xml | 2 -- app/src/main/res/layout/toolbar_content.xml | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/list_dialog.xml b/app/src/main/res/layout/list_dialog.xml index 41290377d..8dcc94417 100644 --- a/app/src/main/res/layout/list_dialog.xml +++ b/app/src/main/res/layout/list_dialog.xml @@ -13,10 +13,8 @@ android:gravity="start" android:maxLines="2" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" android:paddingTop="@dimen/material_grid_margin" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingRight="?android:attr/listPreferredItemPaddingRight" android:paddingBottom="@dimen/material_grid_unit" android:textAppearance="@style/TextAppearance.AppCompat.Title" /> diff --git a/app/src/main/res/layout/toolbar_content.xml b/app/src/main/res/layout/toolbar_content.xml index 4e7c5d103..e1035788f 100644 --- a/app/src/main/res/layout/toolbar_content.xml +++ b/app/src/main/res/layout/toolbar_content.xml @@ -18,7 +18,8 @@ android:id="@+id/home_image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" /> + android:layout_gravity="center" + android:contentDescription="@string/home" /> Date: Mon, 9 Sep 2019 17:46:45 -0300 Subject: [PATCH 02/26] Adding copy action to incognito since we already have share option --- app/src/main/res/menu-large/incognito.xml | 5 ++++- app/src/main/res/menu-xlarge/incognito.xml | 5 ++++- app/src/main/res/menu/incognito.xml | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/menu-large/incognito.xml b/app/src/main/res/menu-large/incognito.xml index 19afb5f02..aa5518a55 100644 --- a/app/src/main/res/menu-large/incognito.xml +++ b/app/src/main/res/menu-large/incognito.xml @@ -27,6 +27,9 @@ + @@ -37,4 +40,4 @@ android:id="@+id/action_reading_mode" android:title="@string/reading_mode"/> - \ No newline at end of file + diff --git a/app/src/main/res/menu-xlarge/incognito.xml b/app/src/main/res/menu-xlarge/incognito.xml index 19afb5f02..aa5518a55 100644 --- a/app/src/main/res/menu-xlarge/incognito.xml +++ b/app/src/main/res/menu-xlarge/incognito.xml @@ -27,6 +27,9 @@ + @@ -37,4 +40,4 @@ android:id="@+id/action_reading_mode" android:title="@string/reading_mode"/> - \ No newline at end of file + diff --git a/app/src/main/res/menu/incognito.xml b/app/src/main/res/menu/incognito.xml index f83616473..a04fae04f 100644 --- a/app/src/main/res/menu/incognito.xml +++ b/app/src/main/res/menu/incognito.xml @@ -10,6 +10,9 @@ + @@ -21,4 +24,4 @@ android:id="@+id/action_reading_mode" android:title="@string/reading_mode"/> - \ No newline at end of file + From 68ac38c87d948e7d93a9844653d4f0e09627750c Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 12 Sep 2019 09:43:53 -0300 Subject: [PATCH 03/26] Use template instead of concatenation --- app/src/main/java/acr/browser/lightning/view/LightningView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.kt b/app/src/main/java/acr/browser/lightning/view/LightningView.kt index 92145eef4..06969f59d 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.kt @@ -444,7 +444,7 @@ class LightningView( */ fun onPause() { webView?.onPause() - logger.log(TAG, "WebView onPause: " + webView?.id) + logger.log(TAG, "WebView onPause: ${webView?.id}") } /** @@ -452,7 +452,7 @@ class LightningView( */ fun onResume() { webView?.onResume() - logger.log(TAG, "WebView onResume: " + webView?.id) + logger.log(TAG, "WebView onResume: ${webView?.id}") } /** From c847e6e978e4355dd005324332426f6116c3cb3b Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Thu, 12 Sep 2019 14:04:10 -0300 Subject: [PATCH 04/26] Upgrading gradle version --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 86482dc72..0b9838270 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip From 154eaefa55328dcb4f29d628316f2c2cf2b26e91 Mon Sep 17 00:00:00 2001 From: iKirby Date: Sat, 14 Sep 2019 18:21:53 +0800 Subject: [PATCH 05/26] Update Simplified Chinese translations --- app/src/main/res/values-zh-rCN/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0523a67de..e809a81e9 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -316,4 +316,10 @@ 从广告屏蔽来源加载 Hosts 失败 + + + 颁发者 + 颁发给 + 颁发时间 + 过期时间 From db527da4805517120120b4e34006862c521cffb0 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 22 Sep 2019 11:04:09 -0400 Subject: [PATCH 06/26] removing i2p submodule in favor of newly published artifacts --- .gitmodules | 3 --- app/build.gradle | 7 ++----- i2p.android.base | 1 - settings.gradle | 7 ------- 4 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 .gitmodules delete mode 160000 i2p.android.base diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 573820b0c..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "i2p.android.base"] - path = i2p.android.base - url = https://github.com/i2p/i2p.android.base diff --git a/app/build.gradle b/app/build.gradle index 2115436f9..70f6b3af4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -144,11 +144,8 @@ dependencies { implementation 'com.anthonycr.grant:permissions:1.1.2' // proxy support - // TODO: Replace I2P submodule with version 0.9.41 when it is available on maven - // implementation 'net.i2p.android:client:0.9.40' - // implementation 'net.i2p.android:helper:0.9.5' - implementation project(':client') - implementation project(':helper') + implementation 'net.i2p.android:client:0.9.42' + implementation 'net.i2p.android:helper:0.9.5' implementation 'com.squareup.okhttp3:okhttp:3.12.3' diff --git a/i2p.android.base b/i2p.android.base deleted file mode 160000 index f3d1e8900..000000000 --- a/i2p.android.base +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f3d1e89002fb0e2cf284a4726b0a98cb437bcf84 diff --git a/settings.gradle b/settings.gradle index 206ff0edf..e7b4def49 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1 @@ include ':app' - -// TODO: Remove :client and :helper projects when 0.9.41 or later of I2P is available on maven -include ':client' -project(':client').projectDir = new File('i2p.android.base/lib/client') - -include ':helper' -project(':helper').projectDir = new File('i2p.android.base/lib/helper') From c77594fa6d51377482fca7f2387bd523cd49c427 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sun, 22 Sep 2019 21:20:45 -0400 Subject: [PATCH 07/26] Fixing bug where the search bar could be focused through the drawers --- .../java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt | 2 ++ app/src/main/res/layout/bookmark_drawer.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt index b648ea775..a385b67c2 100644 --- a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDrawerView.kt @@ -30,6 +30,8 @@ class TabsDrawerView @JvmOverloads constructor( init { orientation = VERTICAL + isClickable = true + isFocusable = true context.inflater.inflate(R.layout.tab_drawer, this, true) actionBack = findViewById(R.id.action_back) actionForward = findViewById(R.id.action_forward) diff --git a/app/src/main/res/layout/bookmark_drawer.xml b/app/src/main/res/layout/bookmark_drawer.xml index 9ba6dd078..cd64279f2 100644 --- a/app/src/main/res/layout/bookmark_drawer.xml +++ b/app/src/main/res/layout/bookmark_drawer.xml @@ -3,6 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + android:clickable="true" + android:focusable="true" android:orientation="vertical"> Date: Tue, 24 Sep 2019 20:06:56 -0400 Subject: [PATCH 08/26] Adding support for fully functional and more secure incognito mode Incognito mode is now run in a separate process that does not share cookies or app cache or web databases with the normal mode. --- app/src/main/AndroidManifest.xml | 1 + .../java/acr/browser/lightning/BrowserApp.kt | 6 +++ .../acr/browser/lightning/Capabilities.kt | 18 ++++++++ .../browser/lightning/IncognitoActivity.kt | 6 ++- .../browser/activity/BrowserActivity.kt | 26 ++--------- .../cleanup/BasicIncognitoExitCleanup.kt | 18 ++++++++ .../browser/cleanup/DelegatingExitCleanup.kt | 26 +++++++++++ .../cleanup/EnhancedIncognitoExitCleanup.kt | 28 ++++++++++++ .../lightning/browser/cleanup/ExitCleanup.kt | 18 ++++++++ .../browser/cleanup/NormalExitCleanup.kt | 44 +++++++++++++++++++ .../browser/lightning/di/AppBindsModule.kt | 5 +++ .../fragment/AdvancedSettingsFragment.kt | 31 ++++++++++--- app/src/main/res/values/strings.xml | 1 + 13 files changed, 197 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/Capabilities.kt create mode 100644 app/src/main/java/acr/browser/lightning/browser/cleanup/BasicIncognitoExitCleanup.kt create mode 100644 app/src/main/java/acr/browser/lightning/browser/cleanup/DelegatingExitCleanup.kt create mode 100644 app/src/main/java/acr/browser/lightning/browser/cleanup/EnhancedIncognitoExitCleanup.kt create mode 100644 app/src/main/java/acr/browser/lightning/browser/cleanup/ExitCleanup.kt create mode 100644 app/src/main/java/acr/browser/lightning/browser/cleanup/NormalExitCleanup.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d238f67b7..07f408930 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -145,6 +145,7 @@ android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|keyboard" android:label="@string/app_name" android:launchMode="singleInstance" + android:process=":incognito" android:parentActivityName=".MainActivity" android:theme="@style/Theme.DarkTheme" android:windowSoftInputMode="stateHidden|adjustResize"> diff --git a/app/src/main/java/acr/browser/lightning/BrowserApp.kt b/app/src/main/java/acr/browser/lightning/BrowserApp.kt index a3ab67ef3..58ff06e9e 100644 --- a/app/src/main/java/acr/browser/lightning/BrowserApp.kt +++ b/app/src/main/java/acr/browser/lightning/BrowserApp.kt @@ -57,6 +57,12 @@ class BrowserApp : Application() { .build()) } + if (Build.VERSION.SDK_INT >= 28) { + if (getProcessName() == "$packageName:incognito") { + WebView.setDataDirectorySuffix("incognito") + } + } + val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler { thread, ex -> diff --git a/app/src/main/java/acr/browser/lightning/Capabilities.kt b/app/src/main/java/acr/browser/lightning/Capabilities.kt new file mode 100644 index 000000000..201b7e6a5 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/Capabilities.kt @@ -0,0 +1,18 @@ +package acr.browser.lightning + +import android.os.Build + +/** + * Capabilities that are specific to certain API levels. + */ +enum class Capabilities { + FULL_INCOGNITO +} + +/** + * Returns true if the capability is supported, false otherwise. + */ +val Capabilities.isSupported: Boolean + get() = when (this) { + Capabilities.FULL_INCOGNITO -> Build.VERSION.SDK_INT >= 28 + } diff --git a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt index 7b6797eba..6c737a3eb 100644 --- a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt +++ b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt @@ -20,7 +20,11 @@ class IncognitoActivity : BrowserActivity() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(this@IncognitoActivity) } - cookieManager.setAcceptCookie(userPreferences.incognitoCookiesEnabled) + if (Capabilities.FULL_INCOGNITO.isSupported) { + cookieManager.setAcceptCookie(userPreferences.cookiesEnabled) + } else { + cookieManager.setAcceptCookie(userPreferences.incognitoCookiesEnabled) + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt index e6cb6deab..c73250331 100644 --- a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt @@ -9,6 +9,7 @@ import acr.browser.lightning.IncognitoActivity import acr.browser.lightning.R import acr.browser.lightning.browser.* import acr.browser.lightning.browser.bookmarks.BookmarksDrawerView +import acr.browser.lightning.browser.cleanup.ExitCleanup import acr.browser.lightning.browser.tabs.TabsDesktopView import acr.browser.lightning.browser.tabs.TabsDrawerView import acr.browser.lightning.controller.UIController @@ -57,10 +58,6 @@ import android.os.Bundle import android.os.Handler import android.os.Message import android.provider.MediaStore -import android.text.Editable -import android.text.TextWatcher -import android.text.style.CharacterStyle -import android.text.style.ParagraphStyle import android.view.* import android.view.View.* import android.view.ViewGroup.LayoutParams @@ -159,6 +156,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr @Inject lateinit var proxyUtils: ProxyUtils @Inject lateinit var logger: Logger @Inject lateinit var bookmarksDialogBuilder: LightningDialogBuilder + @Inject lateinit var exitCleanup: ExitCleanup // Image private var webPageBitmap: Bitmap? = null @@ -986,25 +984,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr } protected fun performExitCleanUp() { - val currentTab = tabsManager.currentTab - if (userPreferences.clearCacheExit && currentTab != null && !isIncognito()) { - WebUtils.clearCache(currentTab.webView) - logger.log(TAG, "Cache Cleared") - } - if (userPreferences.clearHistoryExitEnabled && !isIncognito()) { - WebUtils.clearHistory(this, historyModel, databaseScheduler) - logger.log(TAG, "History Cleared") - } - if (userPreferences.clearCookiesExitEnabled && !isIncognito()) { - WebUtils.clearCookies(this) - logger.log(TAG, "Cookies Cleared") - } - if (userPreferences.clearWebStorageExitEnabled && !isIncognito()) { - WebUtils.clearWebStorage() - logger.log(TAG, "WebStorage Cleared") - } else if (isIncognito()) { - WebUtils.clearWebStorage() // We want to make sure incognito mode is secure - } + exitCleanup.cleanUp(tabsManager.currentTab?.webView, this) } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/app/src/main/java/acr/browser/lightning/browser/cleanup/BasicIncognitoExitCleanup.kt b/app/src/main/java/acr/browser/lightning/browser/cleanup/BasicIncognitoExitCleanup.kt new file mode 100644 index 000000000..d3e7a474b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/cleanup/BasicIncognitoExitCleanup.kt @@ -0,0 +1,18 @@ +package acr.browser.lightning.browser.cleanup + +import acr.browser.lightning.browser.activity.BrowserActivity +import acr.browser.lightning.utils.WebUtils +import android.webkit.WebView +import javax.inject.Inject + +/** + * Exit cleanup that should run on API < 28 when the incognito instance is closed. This is + * significantly less secure than on API > 28 since we can separate WebView data from + */ +class BasicIncognitoExitCleanup @Inject constructor() : ExitCleanup { + override fun cleanUp(webView: WebView?, context: BrowserActivity) { + // We want to make sure incognito mode is secure as possible without also breaking existing + // browser instances. + WebUtils.clearWebStorage() + } +} diff --git a/app/src/main/java/acr/browser/lightning/browser/cleanup/DelegatingExitCleanup.kt b/app/src/main/java/acr/browser/lightning/browser/cleanup/DelegatingExitCleanup.kt new file mode 100644 index 000000000..56bf8b76a --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/cleanup/DelegatingExitCleanup.kt @@ -0,0 +1,26 @@ +package acr.browser.lightning.browser.cleanup + +import acr.browser.lightning.Capabilities +import acr.browser.lightning.MainActivity +import acr.browser.lightning.browser.activity.BrowserActivity +import acr.browser.lightning.isSupported +import android.webkit.WebView +import javax.inject.Inject + +/** + * Exit cleanup that determines which sort of cleanup to do at runtime. It determines which cleanup + * to perform based on the API version and whether we are in incognito mode or normal mode. + */ +class DelegatingExitCleanup @Inject constructor( + private val basicIncognitoExitCleanup: BasicIncognitoExitCleanup, + private val enhancedIncognitoExitCleanup: EnhancedIncognitoExitCleanup, + private val normalExitCleanup: NormalExitCleanup +) : ExitCleanup { + override fun cleanUp(webView: WebView?, context: BrowserActivity) { + when { + context is MainActivity -> normalExitCleanup.cleanUp(webView, context) + Capabilities.FULL_INCOGNITO.isSupported -> enhancedIncognitoExitCleanup.cleanUp(webView, context) + else -> basicIncognitoExitCleanup.cleanUp(webView, context) + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/browser/cleanup/EnhancedIncognitoExitCleanup.kt b/app/src/main/java/acr/browser/lightning/browser/cleanup/EnhancedIncognitoExitCleanup.kt new file mode 100644 index 000000000..eefd6c07e --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/cleanup/EnhancedIncognitoExitCleanup.kt @@ -0,0 +1,28 @@ +package acr.browser.lightning.browser.cleanup + +import acr.browser.lightning.browser.activity.BrowserActivity +import acr.browser.lightning.log.Logger +import acr.browser.lightning.utils.WebUtils +import android.webkit.WebView +import javax.inject.Inject + +/** + * Exit cleanup that should be run when the incognito process is exited on API >= 28. This cleanup + * clears cookies and all web data, which can be done without affecting + */ +class EnhancedIncognitoExitCleanup @Inject constructor( + private val logger: Logger +) : ExitCleanup { + override fun cleanUp(webView: WebView?, context: BrowserActivity) { + WebUtils.clearCache(webView) + logger.log(TAG, "Cache Cleared") + WebUtils.clearCookies(context) + logger.log(TAG, "Cookies Cleared") + WebUtils.clearWebStorage() + logger.log(TAG, "WebStorage Cleared") + } + + companion object { + private const val TAG = "EnhancedIncognitoExitCleanup" + } +} diff --git a/app/src/main/java/acr/browser/lightning/browser/cleanup/ExitCleanup.kt b/app/src/main/java/acr/browser/lightning/browser/cleanup/ExitCleanup.kt new file mode 100644 index 000000000..796fa2f28 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/cleanup/ExitCleanup.kt @@ -0,0 +1,18 @@ +package acr.browser.lightning.browser.cleanup + +import acr.browser.lightning.browser.activity.BrowserActivity +import android.webkit.WebView + +/** + * A command that runs as the browser instance is shutting down to clean up anything that needs to + * be cleaned up. For instance, if the user has chosen to clear cache on exit or if incognito mode + * is closing. + */ +interface ExitCleanup { + + /** + * Clean up the instance of the browser with the provided [webView] and [context]. + */ + fun cleanUp(webView: WebView?, context: BrowserActivity) + +} diff --git a/app/src/main/java/acr/browser/lightning/browser/cleanup/NormalExitCleanup.kt b/app/src/main/java/acr/browser/lightning/browser/cleanup/NormalExitCleanup.kt new file mode 100644 index 000000000..73b7a3d72 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/cleanup/NormalExitCleanup.kt @@ -0,0 +1,44 @@ +package acr.browser.lightning.browser.cleanup + +import acr.browser.lightning.browser.activity.BrowserActivity +import acr.browser.lightning.database.history.HistoryDatabase +import acr.browser.lightning.di.DatabaseScheduler +import acr.browser.lightning.log.Logger +import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.utils.WebUtils +import android.webkit.WebView +import io.reactivex.Scheduler +import javax.inject.Inject + +/** + * Exit cleanup that should run whenever the main browser process is exiting. + */ +class NormalExitCleanup @Inject constructor( + private val userPreferences: UserPreferences, + private val logger: Logger, + private val historyDatabase: HistoryDatabase, + @DatabaseScheduler private val databaseScheduler: Scheduler +) : ExitCleanup { + override fun cleanUp(webView: WebView?, context: BrowserActivity) { + if (userPreferences.clearCacheExit) { + WebUtils.clearCache(webView) + logger.log(TAG, "Cache Cleared") + } + if (userPreferences.clearHistoryExitEnabled) { + WebUtils.clearHistory(context, historyDatabase, databaseScheduler) + logger.log(TAG, "History Cleared") + } + if (userPreferences.clearCookiesExitEnabled) { + WebUtils.clearCookies(context) + logger.log(TAG, "Cookies Cleared") + } + if (userPreferences.clearWebStorageExitEnabled) { + WebUtils.clearWebStorage() + logger.log(TAG, "WebStorage Cleared") + } + } + + companion object { + const val TAG = "NormalExitCleanup" + } +} diff --git a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt index eee6e18fa..1fa16afa5 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt @@ -6,6 +6,8 @@ import acr.browser.lightning.adblock.source.AssetsHostsDataSource import acr.browser.lightning.adblock.source.HostsDataSource import acr.browser.lightning.adblock.source.HostsDataSourceProvider import acr.browser.lightning.adblock.source.PreferencesHostsDataSourceProvider +import acr.browser.lightning.browser.cleanup.DelegatingExitCleanup +import acr.browser.lightning.browser.cleanup.ExitCleanup import acr.browser.lightning.database.adblock.HostsDatabase import acr.browser.lightning.database.adblock.HostsRepository import acr.browser.lightning.database.allowlist.AdBlockAllowListDatabase @@ -27,6 +29,9 @@ import dagger.Module @Module interface AppBindsModule { + @Binds + fun providesExitCleanup(delegatingExitCleanup: DelegatingExitCleanup): ExitCleanup + @Binds fun provideBookmarkModel(bookmarkDatabase: BookmarkDatabase): BookmarkRepository diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt index 5dbe56157..42f787916 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt @@ -1,11 +1,13 @@ package acr.browser.lightning.settings.fragment +import acr.browser.lightning.Capabilities import acr.browser.lightning.R import acr.browser.lightning.browser.SearchBoxDisplayChoice import acr.browser.lightning.constant.TEXT_ENCODINGS import acr.browser.lightning.di.injector import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.extensions.withSingleChoiceItems +import acr.browser.lightning.isSupported import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.view.RenderingMode import android.os.Bundle @@ -50,16 +52,31 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { onCheckChange = { userPreferences.popupsEnabled = it } ) - checkBoxPreference( - preference = SETTINGS_ENABLE_COOKIES, - isChecked = userPreferences.cookiesEnabled, - onCheckChange = { userPreferences.cookiesEnabled = it } + val incognitoCheckboxPreference = checkBoxPreference( + preference = SETTINGS_COOKIES_INCOGNITO, + isEnabled = !Capabilities.FULL_INCOGNITO.isSupported, + isChecked = if (Capabilities.FULL_INCOGNITO.isSupported) { + userPreferences.cookiesEnabled + } else { + userPreferences.incognitoCookiesEnabled + }, + summary = if (Capabilities.FULL_INCOGNITO.isSupported) { + getString(R.string.incognito_cookies_pie) + } else { + null + }, + onCheckChange = { userPreferences.incognitoCookiesEnabled = it } ) checkBoxPreference( - preference = SETTINGS_COOKIES_INCOGNITO, - isChecked = userPreferences.incognitoCookiesEnabled, - onCheckChange = { userPreferences.incognitoCookiesEnabled = it } + preference = SETTINGS_ENABLE_COOKIES, + isChecked = userPreferences.cookiesEnabled, + onCheckChange = { + userPreferences.cookiesEnabled = it + if (Capabilities.FULL_INCOGNITO.isSupported) { + incognitoCheckboxPreference.isChecked = it + } + } ) checkBoxPreference( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86c69bb71..ae87b9653 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,6 +96,7 @@ USB storage unavailable The storage is busy. To allow downloads, touch \'Turn Off USB Storage\' in the notification. Enable cookies in incognito mode + Incognito mode runs in a separate process that is reset after each session. Incognito cookie preferences are now controlled by the regular cookie preferences. Adobe Flash Manual Auto From 9aa3e75569be66dc111efddc9730c7fb50e7c4fb Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 24 Sep 2019 20:09:22 -0400 Subject: [PATCH 09/26] Rename functions to represent their functionality --- .../browser/lightning/di/AppBindsModule.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt index 1fa16afa5..152f58cf2 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt @@ -30,32 +30,32 @@ import dagger.Module interface AppBindsModule { @Binds - fun providesExitCleanup(delegatingExitCleanup: DelegatingExitCleanup): ExitCleanup + fun bindsExitCleanup(delegatingExitCleanup: DelegatingExitCleanup): ExitCleanup @Binds - fun provideBookmarkModel(bookmarkDatabase: BookmarkDatabase): BookmarkRepository + fun bindsBookmarkModel(bookmarkDatabase: BookmarkDatabase): BookmarkRepository @Binds - fun provideDownloadsModel(downloadsDatabase: DownloadsDatabase): DownloadsRepository + fun bindsDownloadsModel(downloadsDatabase: DownloadsDatabase): DownloadsRepository @Binds - fun providesHistoryModel(historyDatabase: HistoryDatabase): HistoryRepository + fun bindsHistoryModel(historyDatabase: HistoryDatabase): HistoryRepository @Binds - fun providesAdBlockAllowListModel(adBlockAllowListDatabase: AdBlockAllowListDatabase): AdBlockAllowListRepository + fun bindsAdBlockAllowListModel(adBlockAllowListDatabase: AdBlockAllowListDatabase): AdBlockAllowListRepository @Binds - fun providesAllowListModel(sessionAllowListModel: SessionAllowListModel): AllowListModel + fun bindsAllowListModel(sessionAllowListModel: SessionAllowListModel): AllowListModel @Binds - fun providesSslWarningPreferences(sessionSslWarningPreferences: SessionSslWarningPreferences): SslWarningPreferences + fun bindsSslWarningPreferences(sessionSslWarningPreferences: SessionSslWarningPreferences): SslWarningPreferences @Binds - fun providesHostsDataSource(assetsHostsDataSource: AssetsHostsDataSource): HostsDataSource + fun bindsHostsDataSource(assetsHostsDataSource: AssetsHostsDataSource): HostsDataSource @Binds - fun providesHostsRepository(hostsDatabase: HostsDatabase): HostsRepository + fun bindsHostsRepository(hostsDatabase: HostsDatabase): HostsRepository @Binds - fun providesHostsDataSourceProvider(preferencesHostsDataSourceProvider: PreferencesHostsDataSourceProvider): HostsDataSourceProvider + fun bindsHostsDataSourceProvider(preferencesHostsDataSourceProvider: PreferencesHostsDataSourceProvider): HostsDataSourceProvider } From 8a4aaab0878a8c722a399e80e9e619254ea09159 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 24 Sep 2019 21:44:39 -0400 Subject: [PATCH 10/26] Replace ApiUtils with Capabilities --- .../acr/browser/lightning/Capabilities.kt | 6 +++- .../fragment/PrivacySettingsFragment.kt | 15 +++++----- .../acr/browser/lightning/utils/ApiUtils.kt | 28 ------------------- .../lightning/view/LightningWebClient.kt | 8 ++++-- 4 files changed, 17 insertions(+), 40 deletions(-) delete mode 100644 app/src/main/java/acr/browser/lightning/utils/ApiUtils.kt diff --git a/app/src/main/java/acr/browser/lightning/Capabilities.kt b/app/src/main/java/acr/browser/lightning/Capabilities.kt index 201b7e6a5..1d17979cf 100644 --- a/app/src/main/java/acr/browser/lightning/Capabilities.kt +++ b/app/src/main/java/acr/browser/lightning/Capabilities.kt @@ -6,7 +6,9 @@ import android.os.Build * Capabilities that are specific to certain API levels. */ enum class Capabilities { - FULL_INCOGNITO + FULL_INCOGNITO, + WEB_RTC, + THIRD_PARTY_COOKIE_BLOCKING } /** @@ -15,4 +17,6 @@ enum class Capabilities { val Capabilities.isSupported: Boolean get() = when (this) { Capabilities.FULL_INCOGNITO -> Build.VERSION.SDK_INT >= 28 + Capabilities.WEB_RTC -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + Capabilities.THIRD_PARTY_COOKIE_BLOCKING -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP } diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.kt index b8bfa2270..2843b6473 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/PrivacySettingsFragment.kt @@ -1,5 +1,6 @@ package acr.browser.lightning.settings.fragment +import acr.browser.lightning.Capabilities import acr.browser.lightning.R import acr.browser.lightning.database.history.HistoryRepository import acr.browser.lightning.di.DatabaseScheduler @@ -8,8 +9,8 @@ import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog import acr.browser.lightning.dialog.DialogItem import acr.browser.lightning.extensions.snackbar +import acr.browser.lightning.isSupported import acr.browser.lightning.preference.UserPreferences -import acr.browser.lightning.utils.ApiUtils import acr.browser.lightning.utils.WebUtils import acr.browser.lightning.view.LightningView import android.os.Bundle @@ -45,7 +46,7 @@ class PrivacySettingsFragment : AbstractSettingsFragment() { checkBoxPreference( preference = SETTINGS_THIRDPCOOKIES, isChecked = userPreferences.blockThirdPartyCookiesEnabled, - isEnabled = ApiUtils.doesSupportThirdPartyCookieBlocking(), + isEnabled = Capabilities.THIRD_PARTY_COOKIE_BLOCKING.isSupported, onCheckChange = { userPreferences.blockThirdPartyCookiesEnabled = it } ) @@ -81,22 +82,20 @@ class PrivacySettingsFragment : AbstractSettingsFragment() { checkBoxPreference( preference = SETTINGS_DONOTTRACK, - isChecked = userPreferences.doNotTrackEnabled && ApiUtils.doesSupportWebViewHeaders(), - isEnabled = ApiUtils.doesSupportWebViewHeaders(), + isChecked = userPreferences.doNotTrackEnabled, onCheckChange = { userPreferences.doNotTrackEnabled = it } ) checkBoxPreference( preference = SETTINGS_WEBRTC, - isChecked = userPreferences.webRtcEnabled && ApiUtils.doesSupportWebRtc(), - isEnabled = ApiUtils.doesSupportWebRtc(), + isChecked = userPreferences.webRtcEnabled && Capabilities.WEB_RTC.isSupported, + isEnabled = Capabilities.WEB_RTC.isSupported, onCheckChange = { userPreferences.webRtcEnabled = it } ) checkBoxPreference( preference = SETTINGS_IDENTIFYINGHEADERS, - isChecked = userPreferences.removeIdentifyingHeadersEnabled && ApiUtils.doesSupportWebViewHeaders(), - isEnabled = ApiUtils.doesSupportWebViewHeaders(), + isChecked = userPreferences.removeIdentifyingHeadersEnabled, summary = "${LightningView.HEADER_REQUESTED_WITH}, ${LightningView.HEADER_WAP_PROFILE}", onCheckChange = { userPreferences.removeIdentifyingHeadersEnabled = it } ) diff --git a/app/src/main/java/acr/browser/lightning/utils/ApiUtils.kt b/app/src/main/java/acr/browser/lightning/utils/ApiUtils.kt deleted file mode 100644 index e0a3dc058..000000000 --- a/app/src/main/java/acr/browser/lightning/utils/ApiUtils.kt +++ /dev/null @@ -1,28 +0,0 @@ -package acr.browser.lightning.utils - -import android.os.Build - -/** - * Utils to determine the capabilities of the Android version used on the device. - */ -object ApiUtils { - - /** - * Returns true if the Android version supports custom headers in the WebView. - */ - @JvmStatic - fun doesSupportWebViewHeaders(): Boolean = true - - /** - * Returns true if the Android version supports WebRTC in the WebView. - */ - @JvmStatic - fun doesSupportWebRtc(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - - /** - * Returns true if the Android version supports blocking third party cookies in the WebView. - */ - @JvmStatic - fun doesSupportThirdPartyCookieBlocking(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - -} diff --git a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt index 05c3cdedc..200354e24 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt @@ -15,7 +15,10 @@ import acr.browser.lightning.log.Logger import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.ssl.SslState import acr.browser.lightning.ssl.SslWarningPreferences -import acr.browser.lightning.utils.* +import acr.browser.lightning.utils.IntentUtils +import acr.browser.lightning.utils.ProxyUtils +import acr.browser.lightning.utils.Utils +import acr.browser.lightning.utils.isSpecialUrl import android.annotation.TargetApi import android.app.Activity import android.content.ActivityNotFoundException @@ -297,11 +300,10 @@ class LightningWebClient( } return when { headers.isEmpty() -> false - ApiUtils.doesSupportWebViewHeaders() -> { + else -> { webView.loadUrl(url, headers) true } - else -> false } } From ec87363ed492c6a6e115e90e13bb5b9f77e3ee76 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Tue, 24 Sep 2019 23:55:52 -0400 Subject: [PATCH 11/26] Adding tab freezing feature that only loads the latest tab and delays initializing the others until the view is attached. This should save memory and improve performance. --- .../browser/lightning/browser/TabsManager.kt | 29 ++++++++++++++---- .../browser/lightning/view/LightningView.kt | 30 ++++++++++++++++--- .../browser/lightning/view/TabInitializer.kt | 11 ++++++- app/src/main/res/drawable/ic_frozen.xml | 10 +++++++ app/src/main/res/values/strings.xml | 3 ++ 5 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/drawable/ic_frozen.xml diff --git a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt index e7eedcf5d..bd2c5cb3c 100644 --- a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt +++ b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt @@ -1,5 +1,6 @@ package acr.browser.lightning.browser +import acr.browser.lightning.R import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.DiskScheduler import acr.browser.lightning.di.MainScheduler @@ -156,7 +157,7 @@ class TabsManager @Inject constructor( * saved on disk. Can potentially be empty. */ private fun restorePreviousTabs(): Observable = readSavedStateFromDisk() - .map { bundle -> + .map { (bundle, title) -> return@map bundle.getString(URL_KEY)?.let { url -> when { url.isBookmarkUrl() -> bookmarkPageInitializer @@ -165,7 +166,8 @@ class TabsManager @Inject constructor( url.isHistoryUrl() -> historyPageInitializer else -> homePageInitializer } - } ?: BundleInitializer(bundle) + } ?: FreezableBundleInitializer(bundle, title + ?: application.getString(R.string.tab_frozen)) } @@ -327,11 +329,11 @@ class TabsManager @Inject constructor( val outState = Bundle(ClassLoader.getSystemClassLoader()) logger.log(TAG, "Saving tab state") tabList - .filter { it.url.isNotBlank() } .withIndex() .forEach { (index, tab) -> if (!tab.url.isSpecialUrl()) { outState.putBundle(BUNDLE_KEY + index, tab.saveState()) + outState.putString(TAB_TITLE_KEY + index, tab.title) } else { outState.putBundle(BUNDLE_KEY + index, Bundle().apply { putString(URL_KEY, tab.url) @@ -354,15 +356,31 @@ class TabsManager @Inject constructor( * on disk. After the list of bundle [Bundle] is read off disk, the old state will be deleted. * Can potentially be empty. */ - private fun readSavedStateFromDisk(): Observable = Maybe + private fun readSavedStateFromDisk(): Observable> = Maybe .fromCallable { FileUtils.readBundleFromStorage(application, BUNDLE_STORAGE) } .flattenAsObservable { bundle -> bundle.keySet() .filter { it.startsWith(BUNDLE_KEY) } - .mapNotNull(bundle::getBundle) + .mapNotNull { bundleKey -> + bundle.getBundle(bundleKey)?.let { + Pair( + it, + bundle.getString(TAB_TITLE_KEY + bundleKey.extractNumberFromEnd()) + ) + } + } } .doOnNext { logger.log(TAG, "Restoring previous WebView state now") } + private fun String.extractNumberFromEnd(): String { + val underScore = lastIndexOf('_') + return if (underScore in 0 until length) { + substring(underScore + 1) + } else { + "" + } + } + /** * Returns the index of the current tab. * @@ -409,6 +427,7 @@ class TabsManager @Inject constructor( private const val TAG = "TabsManager" private const val BUNDLE_KEY = "WEBVIEW_" + private const val TAB_TITLE_KEY = "TITLE_" private const val URL_KEY = "URL_KEY" private const val BUNDLE_STORAGE = "SAVED_TABS.parcel" } diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.kt b/app/src/main/java/acr/browser/lightning/view/LightningView.kt index 06969f59d..adca483f4 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.kt @@ -4,6 +4,7 @@ package acr.browser.lightning.view +import acr.browser.lightning.R import acr.browser.lightning.constant.DESKTOP_USER_AGENT import acr.browser.lightning.controller.UIController import acr.browser.lightning.di.DatabaseScheduler @@ -11,6 +12,7 @@ import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.download.LightningDownloadListener +import acr.browser.lightning.extensions.drawable import acr.browser.lightning.log.Logger import acr.browser.lightning.network.NetworkConnectivityModel import acr.browser.lightning.preference.UserPreferences @@ -34,6 +36,7 @@ import android.webkit.WebSettings import android.webkit.WebSettings.LayoutAlgorithm import android.webkit.WebView import androidx.collection.ArrayMap +import androidx.core.graphics.drawable.toBitmap import io.reactivex.Observable import io.reactivex.Scheduler import io.reactivex.Single @@ -65,9 +68,15 @@ class LightningView( * Getter for the [LightningViewTitle] of the current LightningView instance. * * @return a NonNull instance of LightningViewTitle + * @return a NonNull instance of LightningViewTitle */ val titleInfo: LightningViewTitle + /** + * A tab initializer that should be run when the view is first attached. + */ + private var latentTabInitializer: FreezableBundleInitializer? = null + /** * Gets the current WebView instance of the tab. * @@ -91,6 +100,12 @@ class LightningView( var isForegroundTab: Boolean = false set(isForeground) { field = isForeground + if (isForeground) { + webView?.let { + latentTabInitializer?.initialize(it, requestHeaders) + latentTabInitializer = null + } + } uiController.tabChanged(this) } /** @@ -221,7 +236,13 @@ class LightningView( } initializePreferences() - tabInitializer.initialize(tab, requestHeaders) + if (tabInitializer !is FreezableBundleInitializer) { + tabInitializer.initialize(tab, requestHeaders) + } else { + latentTabInitializer = tabInitializer + titleInfo.setTitle(tabInitializer.initialTitle) + titleInfo.setFavicon(activity.drawable(R.drawable.ic_frozen).toBitmap()) + } networkDisposable = networkConnectivityModel.connectivity() .observeOn(mainScheduler) @@ -435,9 +456,10 @@ class LightningView( /** * Save the state of the tab and return it as a [Bundle]. */ - fun saveState(): Bundle = Bundle(ClassLoader.getSystemClassLoader()).also { - webView?.saveState(it) - } + fun saveState(): Bundle = latentTabInitializer?.bundle + ?: Bundle(ClassLoader.getSystemClassLoader()).also { + webView?.saveState(it) + } /** * Pause the current WebView instance. diff --git a/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt b/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt index 54e83ec64..23ce5a687 100644 --- a/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt +++ b/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt @@ -144,7 +144,7 @@ class ResultMessageInitializer(private val resultMessage: Message) : TabInitiali /** * An initializer that restores the [WebView] state using the [bundle]. */ -class BundleInitializer(private val bundle: Bundle) : TabInitializer { +open class BundleInitializer(private val bundle: Bundle) : TabInitializer { override fun initialize(webView: WebView, headers: Map) { webView.restoreState(bundle) @@ -152,6 +152,15 @@ class BundleInitializer(private val bundle: Bundle) : TabInitializer { } +/** + * An initializer that can be delayed until the view is attached. [initialTitle] is the title that + * should be initially set on the tab. + */ +class FreezableBundleInitializer( + val bundle: Bundle, + val initialTitle: String +) : BundleInitializer(bundle) + /** * An initializer that does not load anything into the [WebView]. */ diff --git a/app/src/main/res/drawable/ic_frozen.xml b/app/src/main/res/drawable/ic_frozen.xml new file mode 100644 index 000000000..ce047caa5 --- /dev/null +++ b/app/src/main/res/drawable/ic_frozen.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae87b9653..c6182ebef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -339,4 +339,7 @@ \'%1$s\' + + + Frozen… From 4c0bb75f68b6f5c6a691b950c9aa5bb21cbc31c9 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 25 Sep 2019 20:07:29 -0400 Subject: [PATCH 12/26] Add button to search suggestions that inserts the query into the search box --- .../browser/activity/BrowserActivity.kt | 5 ++++- .../lightning/search/SuggestionViewHolder.kt | 1 + .../lightning/search/SuggestionsAdapter.kt | 20 ++++++++++++++++++- app/src/main/res/drawable/ic_insert.xml | 10 ++++++++++ .../main/res/layout/two_line_autocomplete.xml | 16 +++++++++++++-- app/src/main/res/values/strings.xml | 3 +++ 6 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_insert.xml diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt index c73250331..4e7282784 100644 --- a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt @@ -1246,6 +1246,10 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr */ private fun initializeSearchSuggestions(getUrl: AutoCompleteTextView) { suggestionsAdapter = SuggestionsAdapter(this, isIncognito()) + suggestionsAdapter?.onSuggestionInsertClick = { + getUrl.setText(it.title) + getUrl.setSelection(it.title.length) + } getUrl.onItemClickListener = OnItemClickListener { _, _, position, _ -> val url = when (val selection = suggestionsAdapter?.getItem(position) as WebPage) { is HistoryEntry, @@ -1258,7 +1262,6 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr inputMethodManager.hideSoftInputFromWindow(getUrl.windowToken, 0) presenter?.onAutoCompleteItemPressed() } - getUrl.setAdapter(suggestionsAdapter) } diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionViewHolder.kt b/app/src/main/java/acr/browser/lightning/search/SuggestionViewHolder.kt index 1b055a962..fbb438d1b 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionViewHolder.kt +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionViewHolder.kt @@ -9,4 +9,5 @@ class SuggestionViewHolder(view: View) { val imageView: ImageView = view.findViewById(R.id.suggestionIcon) val titleView: TextView = view.findViewById(R.id.title) val urlView: TextView = view.findViewById(R.id.url) + val insertSuggestion: View = view.findViewById(R.id.complete_search) } diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt index 8d272c6ae..ab67a2a93 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt @@ -19,6 +19,8 @@ import acr.browser.lightning.search.suggestions.SuggestionsRepository import android.content.Context import android.view.LayoutInflater import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Filter @@ -52,6 +54,15 @@ class SuggestionsAdapter( private val bookmarkIcon = context.drawable(R.drawable.ic_bookmark) private var suggestionsRepository: SuggestionsRepository + /** + * The listener that is fired when the insert button on a [SearchSuggestion] is clicked. + */ + var onSuggestionInsertClick: ((SearchSuggestion) -> Unit)? = null + + private val onClick = View.OnClickListener { + onSuggestionInsertClick?.invoke(it.tag as SearchSuggestion) + } + private val layoutInflater = LayoutInflater.from(context) init { @@ -100,7 +111,6 @@ class SuggestionsAdapter( override fun getItemId(position: Int): Long = 0 override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val holder: SuggestionViewHolder val finalView: View @@ -126,6 +136,14 @@ class SuggestionsAdapter( holder.imageView.setImageDrawable(image) + holder.insertSuggestion.visibility = if (webPage is SearchSuggestion) { + VISIBLE + } else { + GONE + } + holder.insertSuggestion.tag = webPage + holder.insertSuggestion.setOnClickListener(onClick) + return finalView } diff --git a/app/src/main/res/drawable/ic_insert.xml b/app/src/main/res/drawable/ic_insert.xml new file mode 100644 index 000000000..7b1e33748 --- /dev/null +++ b/app/src/main/res/drawable/ic_insert.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/two_line_autocomplete.xml b/app/src/main/res/layout/two_line_autocomplete.xml index 5deb3c1ad..1839f08f8 100644 --- a/app/src/main/res/layout/two_line_autocomplete.xml +++ b/app/src/main/res/layout/two_line_autocomplete.xml @@ -1,5 +1,6 @@ + android:paddingBottom="3dp" + android:weightSum="1"> @@ -43,4 +46,13 @@ android:textColor="?attr/autoCompleteUrlColor" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6182ebef..b05abdb9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -342,4 +342,7 @@ Frozen… + + + Insert suggestion From e9efb9eed764fa2a0baa68670bbd66e7f7c494f3 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 25 Sep 2019 20:37:41 -0400 Subject: [PATCH 13/26] Allow the suggestion insert button for all suggestions --- .../lightning/browser/activity/BrowserActivity.kt | 9 +++++++-- .../browser/lightning/search/SuggestionsAdapter.kt | 11 ++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt index 4e7282784..8804a33e9 100644 --- a/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/browser/activity/BrowserActivity.kt @@ -1247,8 +1247,13 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr private fun initializeSearchSuggestions(getUrl: AutoCompleteTextView) { suggestionsAdapter = SuggestionsAdapter(this, isIncognito()) suggestionsAdapter?.onSuggestionInsertClick = { - getUrl.setText(it.title) - getUrl.setSelection(it.title.length) + if (it is SearchSuggestion) { + getUrl.setText(it.title) + getUrl.setSelection(it.title.length) + } else { + getUrl.setText(it.url) + getUrl.setSelection(it.url.length) + } } getUrl.onItemClickListener = OnItemClickListener { _, _, position, _ -> val url = when (val selection = suggestionsAdapter?.getItem(position) as WebPage) { diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt index ab67a2a93..8cb324108 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt @@ -19,8 +19,6 @@ import acr.browser.lightning.search.suggestions.SuggestionsRepository import android.content.Context import android.view.LayoutInflater import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Filter @@ -57,10 +55,10 @@ class SuggestionsAdapter( /** * The listener that is fired when the insert button on a [SearchSuggestion] is clicked. */ - var onSuggestionInsertClick: ((SearchSuggestion) -> Unit)? = null + var onSuggestionInsertClick: ((WebPage) -> Unit)? = null private val onClick = View.OnClickListener { - onSuggestionInsertClick?.invoke(it.tag as SearchSuggestion) + onSuggestionInsertClick?.invoke(it.tag as WebPage) } private val layoutInflater = LayoutInflater.from(context) @@ -136,11 +134,6 @@ class SuggestionsAdapter( holder.imageView.setImageDrawable(image) - holder.insertSuggestion.visibility = if (webPage is SearchSuggestion) { - VISIBLE - } else { - GONE - } holder.insertSuggestion.tag = webPage holder.insertSuggestion.setOnClickListener(onClick) From 6dfce5f85992159c6f516abb90184d0d822bd307 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 19:35:26 -0400 Subject: [PATCH 14/26] Removing static app component state in favor of Injector --- .../java/acr/browser/lightning/BrowserApp.kt | 9 +-- .../acr/browser/lightning/di/AppComponent.kt | 6 -- .../acr/browser/lightning/di/DiExtensions.kt | 2 + .../lightning/download/DownloadHandler.java | 39 ++++++----- .../download/LightningDownloadListener.java | 4 +- .../reading/activity/ReadingActivity.java | 4 +- .../browser/lightning/utils/ProxyUtils.java | 64 ++++++++++--------- 7 files changed, 66 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/BrowserApp.kt b/app/src/main/java/acr/browser/lightning/BrowserApp.kt index 58ff06e9e..c979c61ff 100644 --- a/app/src/main/java/acr/browser/lightning/BrowserApp.kt +++ b/app/src/main/java/acr/browser/lightning/BrowserApp.kt @@ -35,7 +35,7 @@ class BrowserApp : Application() { @Inject internal lateinit var logger: Logger @Inject internal lateinit var buildInfo: BuildInfo - val applicationComponent: AppComponent by lazy { appComponent } + lateinit var applicationComponent: AppComponent override fun attachBaseContext(base: Context) { super.attachBaseContext(base) @@ -84,7 +84,7 @@ class BrowserApp : Application() { } } - appComponent = DaggerAppComponent.builder() + applicationComponent = DaggerAppComponent.builder() .application(this) .buildInfo(createBuildInfo()) .build() @@ -123,16 +123,11 @@ class BrowserApp : Application() { }) companion object { - private const val TAG = "BrowserApp" init { AppCompatDelegate.setCompatVectorFromResourcesEnabled(Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) } - - @JvmStatic - lateinit var appComponent: AppComponent - } } diff --git a/app/src/main/java/acr/browser/lightning/di/AppComponent.kt b/app/src/main/java/acr/browser/lightning/di/AppComponent.kt index 96bc45ea0..c2987ebc5 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppComponent.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppComponent.kt @@ -9,14 +9,12 @@ import acr.browser.lightning.browser.activity.ThemableBrowserActivity import acr.browser.lightning.browser.bookmarks.BookmarksDrawerView import acr.browser.lightning.device.BuildInfo import acr.browser.lightning.dialog.LightningDialogBuilder -import acr.browser.lightning.download.DownloadHandler import acr.browser.lightning.download.LightningDownloadListener import acr.browser.lightning.reading.activity.ReadingActivity import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.settings.activity.SettingsActivity import acr.browser.lightning.settings.activity.ThemableSettingsActivity import acr.browser.lightning.settings.fragment.* -import acr.browser.lightning.utils.ProxyUtils import acr.browser.lightning.view.LightningChromeClient import acr.browser.lightning.view.LightningView import acr.browser.lightning.view.LightningWebClient @@ -55,8 +53,6 @@ interface AppComponent { fun inject(app: BrowserApp) - fun inject(proxyUtils: ProxyUtils) - fun inject(activity: ReadingActivity) fun inject(webClient: LightningWebClient) @@ -75,8 +71,6 @@ interface AppComponent { fun inject(chromeClient: LightningChromeClient) - fun inject(downloadHandler: DownloadHandler) - fun inject(searchBoxModel: SearchBoxModel) fun inject(generalSettingsFragment: GeneralSettingsFragment) diff --git a/app/src/main/java/acr/browser/lightning/di/DiExtensions.kt b/app/src/main/java/acr/browser/lightning/di/DiExtensions.kt index 44bae7af5..4532a3129 100644 --- a/app/src/main/java/acr/browser/lightning/di/DiExtensions.kt +++ b/app/src/main/java/acr/browser/lightning/di/DiExtensions.kt @@ -1,3 +1,5 @@ +@file:JvmName("Injector") + package acr.browser.lightning.di import acr.browser.lightning.BrowserApp diff --git a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java index 30ae4c584..b55f4e059 100644 --- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java +++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java @@ -23,7 +23,6 @@ import javax.inject.Inject; import javax.inject.Singleton; -import acr.browser.lightning.BrowserApp; import acr.browser.lightning.BuildConfig; import acr.browser.lightning.MainActivity; import acr.browser.lightning.R; @@ -57,16 +56,26 @@ public class DownloadHandler { private static final String COOKIE_REQUEST_HEADER = "Cookie"; - @Inject DownloadsRepository downloadsRepository; - @Inject DownloadManager downloadManager; - @Inject @DatabaseScheduler Scheduler databaseScheduler; - @Inject @NetworkScheduler Scheduler networkScheduler; - @Inject @MainScheduler Scheduler mainScheduler; - @Inject Logger logger; + private final DownloadsRepository downloadsRepository; + private final DownloadManager downloadManager; + private final Scheduler databaseScheduler; + private final Scheduler networkScheduler; + private final Scheduler mainScheduler; + private final Logger logger; @Inject - public DownloadHandler() { - BrowserApp.getAppComponent().inject(this); + public DownloadHandler(DownloadsRepository downloadsRepository, + DownloadManager downloadManager, + @DatabaseScheduler Scheduler databaseScheduler, + @NetworkScheduler Scheduler networkScheduler, + @MainScheduler Scheduler mainScheduler, + Logger logger) { + this.downloadsRepository = downloadsRepository; + this.downloadManager = downloadManager; + this.databaseScheduler = databaseScheduler; + this.networkScheduler = networkScheduler; + this.mainScheduler = mainScheduler; + this.logger = logger; } /** @@ -77,15 +86,15 @@ public DownloadHandler() { * @param url The full url to the content that should be downloaded * @param userAgent User agent of the downloading application. * @param contentDisposition Content-disposition http header, if present. - * @param mimetype The mimetype of the content reported by the server + * @param mimeType The mimeType of the content reported by the server * @param contentSize The size of the content */ public void onDownloadStart(@NonNull Activity context, @NonNull UserPreferences manager, @NonNull String url, String userAgent, - @Nullable String contentDisposition, String mimetype, @NonNull String contentSize) { + @Nullable String contentDisposition, String mimeType, @NonNull String contentSize) { logger.log(TAG, "DOWNLOAD: Trying to download from URL: " + url); logger.log(TAG, "DOWNLOAD: Content disposition: " + contentDisposition); - logger.log(TAG, "DOWNLOAD: Mimetype: " + mimetype); + logger.log(TAG, "DOWNLOAD: MimeType: " + mimeType); logger.log(TAG, "DOWNLOAD: User agent: " + userAgent); // if we're dealing wih A/V content that's not explicitly marked @@ -95,7 +104,7 @@ public void onDownloadStart(@NonNull Activity context, @NonNull UserPreferences // query the package manager to see if there's a registered handler // that matches. Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(url), mimetype); + intent.setDataAndType(Uri.parse(url), mimeType); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.setComponent(null); @@ -119,7 +128,7 @@ public void onDownloadStart(@NonNull Activity context, @NonNull UserPreferences } } } - onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimetype, contentSize); + onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimeType, contentSize); } // This is to work around the fact that java.net.URI throws Exceptions @@ -299,7 +308,7 @@ private void onDownloadStartNoStream(@NonNull final Activity context, @NonNull U } private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) { - if (fileUri.getPath() == null){ + if (fileUri.getPath() == null) { return false; } File file = new File(fileUri.getPath()); diff --git a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java index a5335333c..b7dbdb17f 100644 --- a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java +++ b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java @@ -16,9 +16,9 @@ import javax.inject.Inject; -import acr.browser.lightning.BrowserApp; import acr.browser.lightning.R; import acr.browser.lightning.database.downloads.DownloadsRepository; +import acr.browser.lightning.di.Injector; import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.log.Logger; import acr.browser.lightning.preference.UserPreferences; @@ -37,7 +37,7 @@ public class LightningDownloadListener implements DownloadListener { @Inject Logger logger; public LightningDownloadListener(Activity context) { - BrowserApp.getAppComponent().inject(this); + Injector.getInjector(context).inject(this); mActivity = context; } diff --git a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java index 0e8a33ff8..472951a37 100644 --- a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java +++ b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java @@ -18,8 +18,8 @@ import javax.inject.Inject; -import acr.browser.lightning.BrowserApp; import acr.browser.lightning.R; +import acr.browser.lightning.di.Injector; import acr.browser.lightning.di.MainScheduler; import acr.browser.lightning.di.NetworkScheduler; import acr.browser.lightning.dialog.BrowserDialog; @@ -79,7 +79,7 @@ public static void launch(@NonNull Context context, @NonNull String url) { @Override protected void onCreate(Bundle savedInstanceState) { - BrowserApp.getAppComponent().inject(this); + Injector.getInjector(this).inject(this); overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale); mInvert = mUserPreferences.getInvertColors(); diff --git a/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java b/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java index 47cbdd5ca..d822e8bd6 100644 --- a/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java @@ -38,13 +38,17 @@ public final class ProxyUtils { private static boolean sI2PHelperBound; private static boolean sI2PProxyInitialized; - @Inject UserPreferences mUserPreferences; - @Inject DeveloperPreferences mDeveloperPreferences; - @Inject I2PAndroidHelper mI2PHelper; + private final UserPreferences userPreferences; + private final DeveloperPreferences developerPreferences; + private final I2PAndroidHelper i2PAndroidHelper; @Inject - public ProxyUtils() { - BrowserApp.getAppComponent().inject(this); + public ProxyUtils(UserPreferences userPreferences, + DeveloperPreferences developerPreferences, + I2PAndroidHelper i2PAndroidHelper) { + this.userPreferences = userPreferences; + this.developerPreferences = developerPreferences; + this.i2PAndroidHelper = i2PAndroidHelper; } /* @@ -52,23 +56,23 @@ public ProxyUtils() { * proxying for this session */ public void checkForProxy(@NonNull final Activity activity) { - final ProxyChoice currentProxyChoice = mUserPreferences.getProxyChoice(); + final ProxyChoice currentProxyChoice = userPreferences.getProxyChoice(); final boolean orbotInstalled = OrbotHelper.isOrbotInstalled(activity); - boolean orbotChecked = mDeveloperPreferences.getCheckedForTor(); + boolean orbotChecked = developerPreferences.getCheckedForTor(); boolean orbot = orbotInstalled && !orbotChecked; - boolean i2pInstalled = mI2PHelper.isI2PAndroidInstalled(); - boolean i2pChecked = mDeveloperPreferences.getCheckedForI2P(); + boolean i2pInstalled = i2PAndroidHelper.isI2PAndroidInstalled(); + boolean i2pChecked = developerPreferences.getCheckedForI2P(); boolean i2p = i2pInstalled && !i2pChecked; // Do only once per install if (currentProxyChoice != ProxyChoice.NONE && (orbot || i2p)) { if (orbot) { - mDeveloperPreferences.setCheckedForTor(true); + developerPreferences.setCheckedForTor(true); } if (i2p) { - mDeveloperPreferences.setCheckedForI2P(true); + developerPreferences.setCheckedForI2P(true); } AlertDialog.Builder builder = new AlertDialog.Builder(activity); @@ -80,13 +84,13 @@ public void checkForProxy(@NonNull final Activity activity) { list.add(new Pair<>(proxyChoice, proxyChoices[proxyChoice.getValue()])); } builder.setTitle(activity.getResources().getString(R.string.http_proxy)); - AlertDialogExtensionsKt.withSingleChoiceItems(builder, list, mUserPreferences.getProxyChoice(), newProxyChoice -> { - mUserPreferences.setProxyChoice(newProxyChoice); + AlertDialogExtensionsKt.withSingleChoiceItems(builder, list, userPreferences.getProxyChoice(), newProxyChoice -> { + userPreferences.setProxyChoice(newProxyChoice); return Unit.INSTANCE; }); builder.setPositiveButton(activity.getResources().getString(R.string.action_ok), (dialog, which) -> { - if (mUserPreferences.getProxyChoice() != ProxyChoice.NONE) { + if (userPreferences.getProxyChoice() != ProxyChoice.NONE) { initializeProxy(activity); } }); @@ -94,13 +98,13 @@ public void checkForProxy(@NonNull final Activity activity) { DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { switch (which) { case DialogInterface.BUTTON_POSITIVE: - mUserPreferences.setProxyChoice(orbotInstalled + userPreferences.setProxyChoice(orbotInstalled ? ProxyChoice.ORBOT : ProxyChoice.I2P); initializeProxy(activity); break; case DialogInterface.BUTTON_NEGATIVE: - mUserPreferences.setProxyChoice(ProxyChoice.NONE); + userPreferences.setProxyChoice(ProxyChoice.NONE); break; } }; @@ -121,7 +125,7 @@ private void initializeProxy(@NonNull Activity activity) { String host; int port; - switch (mUserPreferences.getProxyChoice()) { + switch (userPreferences.getProxyChoice()) { case NONE: // We shouldn't be here return; @@ -134,16 +138,16 @@ private void initializeProxy(@NonNull Activity activity) { break; case I2P: sI2PProxyInitialized = true; - if (sI2PHelperBound && !mI2PHelper.isI2PAndroidRunning()) { - mI2PHelper.requestI2PAndroidStart(activity); + if (sI2PHelperBound && !i2PAndroidHelper.isI2PAndroidRunning()) { + i2PAndroidHelper.requestI2PAndroidStart(activity); } host = "localhost"; port = 4444; break; default: case MANUAL: - host = mUserPreferences.getProxyHost(); - port = mUserPreferences.getProxyPort(); + host = userPreferences.getProxyHost(); + port = userPreferences.getProxyPort(); break; } @@ -156,11 +160,11 @@ private void initializeProxy(@NonNull Activity activity) { } public boolean isProxyReady(@NonNull Activity activity) { - if (mUserPreferences.getProxyChoice() == ProxyChoice.I2P) { - if (!mI2PHelper.isI2PAndroidRunning()) { + if (userPreferences.getProxyChoice() == ProxyChoice.I2P) { + if (!i2PAndroidHelper.isI2PAndroidRunning()) { ActivityExtensions.snackbar(activity, R.string.i2p_not_running); return false; - } else if (!mI2PHelper.areTunnelsActive()) { + } else if (!i2PAndroidHelper.areTunnelsActive()) { ActivityExtensions.snackbar(activity, R.string.i2p_tunnels_not_ready); return false; } @@ -170,7 +174,7 @@ public boolean isProxyReady(@NonNull Activity activity) { } public void updateProxySettings(@NonNull Activity activity) { - if (mUserPreferences.getProxyChoice() != ProxyChoice.NONE) { + if (userPreferences.getProxyChoice() != ProxyChoice.NONE) { initializeProxy(activity); } else { try { @@ -184,17 +188,17 @@ public void updateProxySettings(@NonNull Activity activity) { } public void onStop() { - mI2PHelper.unbind(); + i2PAndroidHelper.unbind(); sI2PHelperBound = false; } public void onStart(final Activity activity) { - if (mUserPreferences.getProxyChoice() == ProxyChoice.I2P) { + if (userPreferences.getProxyChoice() == ProxyChoice.I2P) { // Try to bind to I2P Android - mI2PHelper.bind(() -> { + i2PAndroidHelper.bind(() -> { sI2PHelperBound = true; - if (sI2PProxyInitialized && !mI2PHelper.isI2PAndroidRunning()) - mI2PHelper.requestI2PAndroidStart(activity); + if (sI2PProxyInitialized && !i2PAndroidHelper.isI2PAndroidRunning()) + i2PAndroidHelper.requestI2PAndroidStart(activity); }); } } From 3ffdcff0d5027531e9374cdd0fe576dd072bcbe6 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 19:51:09 -0400 Subject: [PATCH 15/26] Simplifying code --- app/src/main/java/acr/browser/lightning/Capabilities.kt | 4 ++-- .../main/java/acr/browser/lightning/IncognitoActivity.kt | 2 +- .../main/java/acr/browser/lightning/icon/TabCountView.kt | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/Capabilities.kt b/app/src/main/java/acr/browser/lightning/Capabilities.kt index 1d17979cf..5099f16be 100644 --- a/app/src/main/java/acr/browser/lightning/Capabilities.kt +++ b/app/src/main/java/acr/browser/lightning/Capabilities.kt @@ -17,6 +17,6 @@ enum class Capabilities { val Capabilities.isSupported: Boolean get() = when (this) { Capabilities.FULL_INCOGNITO -> Build.VERSION.SDK_INT >= 28 - Capabilities.WEB_RTC -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - Capabilities.THIRD_PARTY_COOKIE_BLOCKING -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + Capabilities.WEB_RTC -> Build.VERSION.SDK_INT >= 21 + Capabilities.THIRD_PARTY_COOKIE_BLOCKING -> Build.VERSION.SDK_INT >= 21 } diff --git a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt index 6c737a3eb..bf1eebb6c 100644 --- a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt +++ b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt @@ -45,7 +45,7 @@ class IncognitoActivity : BrowserActivity() { override fun isIncognito() = true - override fun closeActivity() = closeDrawers(this::closeBrowser) + override fun closeActivity() = closeDrawers(::closeBrowser) companion object { /** diff --git a/app/src/main/java/acr/browser/lightning/icon/TabCountView.kt b/app/src/main/java/acr/browser/lightning/icon/TabCountView.kt index 742ea22bb..d528093ce 100644 --- a/app/src/main/java/acr/browser/lightning/icon/TabCountView.kt +++ b/app/src/main/java/acr/browser/lightning/icon/TabCountView.kt @@ -61,7 +61,7 @@ class TabCountView @JvmOverloads constructor( } override fun onDraw(canvas: Canvas) { - val text: String = if (count > 99) { + val text: String = if (count > MAX_DISPLAYABLE_NUMBER) { context.getString(R.string.infinity) } else { numberFormat.format(count) @@ -88,4 +88,8 @@ class TabCountView @JvmOverloads constructor( super.onDraw(canvas) } + companion object { + private const val MAX_DISPLAYABLE_NUMBER = 99 + } + } From ed4c2d444877266521f48339ae09d8185656a2a2 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 19:51:42 -0400 Subject: [PATCH 16/26] Move backpressure strategy declaration up to avoid processing strings we can't receive --- .../java/acr/browser/lightning/search/SuggestionsAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt index 8cb324108..ee5d94d96 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.kt @@ -163,9 +163,9 @@ class SuggestionsAdapter( } private fun Observable.results(): Flowable> = this + .toFlowable(BackpressureStrategy.LATEST) .map { it.toString().toLowerCase(Locale.getDefault()).trim() } .filter(String::isNotEmpty) - .toFlowable(BackpressureStrategy.LATEST) .share() .compose { upstream -> val searchEntries = upstream From ea20c704d22a5cb48143ae0ede59cb704860fdc4 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 20:12:42 -0400 Subject: [PATCH 17/26] Simplifying interpolator implementation --- .../interpolator/BezierDecelerateInterpolator.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.kt b/app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.kt index cff74b2aa..fa44d9cee 100644 --- a/app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.kt +++ b/app/src/main/java/acr/browser/lightning/interpolator/BezierDecelerateInterpolator.kt @@ -1,19 +1,15 @@ package acr.browser.lightning.interpolator -import androidx.core.view.animation.PathInterpolatorCompat import android.view.animation.Interpolator +import androidx.core.view.animation.PathInterpolatorCompat /** - * Bezier decelerate curve similar to iOS. - * On Kitkat and below, it reverts to a - * decelerate interpolator. + * Smooth bezier curve interpolator. */ class BezierDecelerateInterpolator : Interpolator { - companion object { - private val PATH_INTERPOLATOR: Interpolator = PathInterpolatorCompat.create(0.25f, 0.1f, 0.25f, 1f) - } + private val interpolator = PathInterpolatorCompat.create(0.25f, 0.1f, 0.25f, 1f) - override fun getInterpolation(input: Float): Float = PATH_INTERPOLATOR.getInterpolation(input) + override fun getInterpolation(input: Float): Float = interpolator.getInterpolation(input) } From f2f2fcd84e91706dc5e9c21f6d38505c31930b98 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 20:46:04 -0400 Subject: [PATCH 18/26] Renaming function to be more clear in its usage --- .../main/java/acr/browser/lightning/browser/SearchBoxModel.kt | 2 +- .../acr/browser/lightning/reading/activity/ReadingActivity.java | 2 +- app/src/main/java/acr/browser/lightning/utils/Utils.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt b/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt index 82af137fa..286e8f682 100644 --- a/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt +++ b/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt @@ -50,6 +50,6 @@ class SearchBoxModel @Inject constructor( } } - private fun safeDomain(url: String) = Utils.getDomainName(url) + private fun safeDomain(url: String) = Utils.getDisplayDomainName(url) } diff --git a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java index 472951a37..191cb3ec2 100644 --- a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java +++ b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java @@ -151,7 +151,7 @@ private boolean loadPage(@Nullable Intent intent) { return false; } if (getSupportActionBar() != null) { - getSupportActionBar().setTitle(Utils.getDomainName(mUrl)); + getSupportActionBar().setTitle(Utils.getDisplayDomainName(mUrl)); } mProgressDialog = new ProgressDialog(ReadingActivity.this); diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java index 0e5791723..ef8487950 100644 --- a/app/src/main/java/acr/browser/lightning/utils/Utils.java +++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java @@ -109,7 +109,7 @@ public static int dpToPx(float dp) { * HTTPS if the URL is an SSL supported URL. */ @NonNull - public static String getDomainName(@Nullable String url) { + public static String getDisplayDomainName(@Nullable String url) { if (url == null || url.isEmpty()) return ""; boolean ssl = URLUtil.isHttpsUrl(url); From e7317b9b318d51eec21adf77653ee33da5b9fe6e Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 27 Sep 2019 23:09:09 -0400 Subject: [PATCH 19/26] Simplifying domain parsing logic --- .../lightning/adblock/BloomFilterAdBlocker.kt | 55 +++++++------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/adblock/BloomFilterAdBlocker.kt b/app/src/main/java/acr/browser/lightning/adblock/BloomFilterAdBlocker.kt index bd4fbabbf..cce00d941 100644 --- a/app/src/main/java/acr/browser/lightning/adblock/BloomFilterAdBlocker.kt +++ b/app/src/main/java/acr/browser/lightning/adblock/BloomFilterAdBlocker.kt @@ -130,51 +130,34 @@ class BloomFilterAdBlocker @Inject constructor( } override fun isAd(url: String): Boolean { - val domain = try { - getDomainName(url) - } catch (exception: URISyntaxException) { - logger.log(TAG, "URL '$url' is invalid", exception) - return false - } + val domain = url.host() ?: return false val mightBeOnBlockList = bloomFilter.mightContain(domain) - return if (mightBeOnBlockList) { - val isOnBlockList = hostsRepository.containsHost(domain) - if (isOnBlockList) { - logger.log(TAG, "URL '$url' is an ad") - } else { - logger.log(TAG, "False positive for $url") - } + return when { + mightBeOnBlockList -> { + val isOnBlockList = hostsRepository.containsHost(domain) + if (isOnBlockList) { + logger.log(TAG, "URL '$url' is an ad") + } else { + logger.log(TAG, "False positive for $url") + } - isOnBlockList - } else { - false + isOnBlockList + } + domain.name.startsWith("www.") -> isAd(domain.name.substring(4)) + else -> false } } /** - * Returns the probable domain name for a given URL - * - * @param url the url to parse - * @return returns the domain - * @throws URISyntaxException throws an exception if the string cannot form a URI + * Extract the [Host] from a [String] representing a URL. Returns null if no host was extracted. */ - @Throws(URISyntaxException::class) - private fun getDomainName(url: String): Host { - val host = url.indexOf('/', 8) - .takeIf { it != -1 } - ?.let(url::take) - ?: url - - val uri = URI(host) - val domain = uri.host ?: return Host(host) - - return Host(if (domain.startsWith("www.")) { - domain.substring(4) - } else { - domain - }) + private fun String.host(): Host? = try { + URI(this).host?.let(::Host) + } catch (exception: URISyntaxException) { + logger.log(TAG, "Invalid URL: $this", exception) + null } companion object { From 3592899755005076c64ffa89afe76f67e5468da6 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 28 Sep 2019 00:21:12 -0400 Subject: [PATCH 20/26] Making query more correct It was working before, but this is more correct and returns less data. --- .../acr/browser/lightning/database/adblock/HostsDatabase.kt | 3 ++- .../acr/browser/lightning/database/history/HistoryDatabase.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/database/adblock/HostsDatabase.kt b/app/src/main/java/acr/browser/lightning/database/adblock/HostsDatabase.kt index 48330d922..9bb7b86ce 100644 --- a/app/src/main/java/acr/browser/lightning/database/adblock/HostsDatabase.kt +++ b/app/src/main/java/acr/browser/lightning/database/adblock/HostsDatabase.kt @@ -70,11 +70,12 @@ class HostsDatabase @Inject constructor( override fun containsHost(host: Host): Boolean { database.query( TABLE_HOSTS, - null, + arrayOf(KEY_ID), "$KEY_NAME=?", arrayOf(host.name), null, null, + null, "1" ).safeUse { return it.moveToFirst() diff --git a/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.kt b/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.kt index b7be5a6b5..c6fd3b160 100644 --- a/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.kt +++ b/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.kt @@ -70,7 +70,7 @@ class HistoryDatabase @Inject constructor( database.query( false, TABLE_HISTORY, - arrayOf(KEY_URL), + arrayOf(KEY_ID), "$KEY_URL = ?", arrayOf(url), null, From effacc6a6ec700f32ec5e069b931bbeab2061f6f Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 28 Sep 2019 15:05:21 -0400 Subject: [PATCH 21/26] Deduplicate interceptor creation --- .../acr/browser/lightning/di/AppModule.kt | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/di/AppModule.kt b/app/src/main/java/acr/browser/lightning/di/AppModule.kt index da885839c..9cde54745 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppModule.kt @@ -120,14 +120,18 @@ class AppModule { @Singleton @Provides fun providesSuggestionsRequestFactory(cacheControl: CacheControl): RequestFactory = object : RequestFactory { - override fun createSuggestionsRequest(httpUrl: HttpUrl, encoding: String): Request { return Request.Builder().url(httpUrl) .addHeader("Accept-Charset", encoding) .cacheControl(cacheControl) .build() } + } + private fun createInterceptorWithMaxCacheAge(maxCacheAgeSeconds: Long) = Interceptor { chain -> + chain.proceed(chain.request()).newBuilder() + .header("cache-control", "max-age=$maxCacheAgeSeconds, max-stale=$maxCacheAgeSeconds") + .build() } @Singleton @@ -135,19 +139,11 @@ class AppModule { @SuggestionsClient fun providesSuggestionsHttpClient(application: Application): Single = Single.fromCallable { val intervalDay = TimeUnit.DAYS.toSeconds(1) - - val rewriteCacheControlInterceptor = Interceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") - .build() - } - val suggestionsCache = File(application.cacheDir, "suggestion_responses") return@fromCallable OkHttpClient.Builder() .cache(Cache(suggestionsCache, FileUtils.megabytesToBytes(1))) - .addNetworkInterceptor(rewriteCacheControlInterceptor) + .addNetworkInterceptor(createInterceptorWithMaxCacheAge(intervalDay)) .build() }.cache() @@ -155,20 +151,12 @@ class AppModule { @Provides @HostsClient fun providesHostsHttpClient(application: Application): Single = Single.fromCallable { - val intervalDay = TimeUnit.DAYS.toSeconds(365) - - val rewriteCacheControlInterceptor = Interceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") - .build() - } - + val intervalYear = TimeUnit.DAYS.toSeconds(365) val suggestionsCache = File(application.cacheDir, "hosts_cache") return@fromCallable OkHttpClient.Builder() .cache(Cache(suggestionsCache, FileUtils.megabytesToBytes(5))) - .addNetworkInterceptor(rewriteCacheControlInterceptor) + .addNetworkInterceptor(createInterceptorWithMaxCacheAge(intervalYear)) .build() }.cache() From 0adf20823b47d5650b84ba15a89f340fba9ac965 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 28 Sep 2019 15:20:49 -0400 Subject: [PATCH 22/26] Simplify rx observable declarations --- .../main/java/acr/browser/lightning/browser/TabsManager.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt index bd2c5cb3c..55246f9f1 100644 --- a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt +++ b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt @@ -102,7 +102,7 @@ class TabsManager @Inject constructor( .subscribeOn(mainScheduler) .observeOn(databaseScheduler) .flatMapObservable { - return@flatMapObservable if (incognito) { + if (incognito) { initializeIncognitoMode(it.value()) } else { initializeRegularMode(it.value(), activity) @@ -117,9 +117,7 @@ class TabsManager @Inject constructor( * Returns an [Observable] that emits the [TabInitializer] for incognito mode. */ private fun initializeIncognitoMode(initialUrl: String?): Observable = - Observable.fromCallable { - return@fromCallable initialUrl?.let(::UrlInitializer) ?: homePageInitializer - } + Observable.fromCallable { initialUrl?.let(::UrlInitializer) ?: homePageInitializer } /** * Returns an [Observable] that emits the [TabInitializer] for normal operation mode. From 3a62345cf738a344cfcde11acfe0563fa8d31824 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Sat, 28 Sep 2019 15:29:14 -0400 Subject: [PATCH 23/26] Allow multiple windows in incognito and support for storage capabilities for full incognito capable API versions --- .../acr/browser/lightning/view/LightningView.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.kt b/app/src/main/java/acr/browser/lightning/view/LightningView.kt index adca483f4..e522853a6 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.kt @@ -4,6 +4,7 @@ package acr.browser.lightning.view +import acr.browser.lightning.Capabilities import acr.browser.lightning.R import acr.browser.lightning.constant.DESKTOP_USER_AGENT import acr.browser.lightning.controller.UIController @@ -13,6 +14,7 @@ import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.download.LightningDownloadListener import acr.browser.lightning.extensions.drawable +import acr.browser.lightning.isSupported import acr.browser.lightning.log.Logger import acr.browser.lightning.network.NetworkConnectivityModel import acr.browser.lightning.preference.UserPreferences @@ -348,12 +350,8 @@ class LightningView( } settings.blockNetworkImage = userPreferences.blockImagesEnabled - if (!isIncognito) { - // Modifying headers causes SEGFAULTS, so disallow multi window if headers are enabled. - settings.setSupportMultipleWindows(userPreferences.popupsEnabled && !modifiesHeaders) - } else { - settings.setSupportMultipleWindows(false) - } + // Modifying headers causes SEGFAULTS, so disallow multi window if headers are enabled. + settings.setSupportMultipleWindows(userPreferences.popupsEnabled && !modifiesHeaders) settings.useWideViewPort = userPreferences.useWideViewPortEnabled settings.loadWithOverviewMode = userPreferences.overviewModeEnabled @@ -389,11 +387,11 @@ class LightningView( mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW } - if (!isIncognito) { + if (!isIncognito || Capabilities.FULL_INCOGNITO.isSupported) { domStorageEnabled = true setAppCacheEnabled(true) - cacheMode = WebSettings.LOAD_DEFAULT databaseEnabled = true + cacheMode = WebSettings.LOAD_DEFAULT } else { domStorageEnabled = false setAppCacheEnabled(false) From 09245f907b4eaa7aaca3fea271e06c3df5a65553 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 30 Sep 2019 20:12:21 -0400 Subject: [PATCH 24/26] Prepare for 5.1.0 release --- CHANGELOG.md | 8 ++++++++ app/build.gradle | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 289a9a31f..853097667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change Log ========== +Version 5.1.0 *(2019-10-01)* +---------------------------- +- Made copy link action is available in incognito mode. +- Fixed bug with bookmark and tab drawer transparency. +- Added feature to freeze old tabs until they are accessed if the app restarts. +- Added button to search suggestions layout that inserts the suggestion rather than searching for it. +- Added support for full sandboxed incognito mode on Android Pie (API 28) and up. + Version 5.0.2 *(2019-09-07)* ---------------------------- - Target Android 10 API 29 diff --git a/app/build.gradle b/app/build.gradle index 70f6b3af4..f56a21218 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ android { defaultConfig { minSdkVersion project.minSdkVersion targetSdkVersion project.targetSdkVersion - versionName "5.0.2" + versionName "5.1.0" vectorDrawables.useSupportLibrary = true } @@ -60,14 +60,14 @@ android { dimension "capabilities" buildConfigField "boolean", "FULL_VERSION", "Boolean.parseBoolean(\"true\")" applicationId "acr.browser.lightning" - versionCode 100 + versionCode 101 } lightningLite { dimension "capabilities" buildConfigField "boolean", "FULL_VERSION", "Boolean.parseBoolean(\"false\")" applicationId "acr.browser.barebones" - versionCode 101 + versionCode 102 } } From 96802650ea763396f1e653af13b43b686a983f17 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 30 Sep 2019 20:18:43 -0400 Subject: [PATCH 25/26] Fixing typo in change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 853097667..00ec5d63a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Change Log Version 5.1.0 *(2019-10-01)* ---------------------------- -- Made copy link action is available in incognito mode. +- Made copy link action available in incognito mode. - Fixed bug with bookmark and tab drawer transparency. - Added feature to freeze old tabs until they are accessed if the app restarts. - Added button to search suggestions layout that inserts the suggestion rather than searching for it. From a0c0dddb3c7cb105979b8f6d9e3b1bad28709b64 Mon Sep 17 00:00:00 2001 From: iKirby Date: Tue, 1 Oct 2019 22:36:28 +0800 Subject: [PATCH 26/26] Update Simplified Chinese translations --- app/src/main/res/values-zh-rCN/strings.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e809a81e9..f443e0c05 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -95,6 +95,7 @@ USB 存储不可用 存储设备正忙。要允许下载,请轻触通知中的“关闭 USB 存储” 在无痕模式下启用 Cookie + 无痕模式运行于单独的进程,并在会话结束后重置。无痕模式下的 Cookie 设置由常规的 Cookie 选项控制。 Adobe Flash 手动 自动 @@ -315,11 +316,15 @@ 从广告屏蔽来源加载 Hosts 失败 - - 颁发者 颁发给 颁发时间 过期时间 + + + 已冻结… + + + 插入搜索建议