From 2679edc697a39281b3be1218229010d42c8d6b37 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Fri, 19 Oct 2018 15:35:20 +1100 Subject: [PATCH 1/2] WIP with places autocomplete working in the reference browser --- README-places.md | 41 +++++++++ app/build.gradle | 2 + .../browser/BrowserAutocompleteProvider.kt | 83 +++++++++++++++++++ .../browser/browser/BrowserFragment.kt | 3 + build.gradle | 2 + buildSrc/src/main/java/Dependencies.kt | 2 + 6 files changed, 133 insertions(+) create mode 100644 README-places.md create mode 100644 app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt diff --git a/README-places.md b/README-places.md new file mode 100644 index 000000000..d0b8cce96 --- /dev/null +++ b/README-places.md @@ -0,0 +1,41 @@ +This branch is a WIP for hooking up the "places" component in the reference +browser. It will only work with +[this android-components branch](https://github.com/mhammond/android-components/tree/autocomplete-places), +and in particular, after following +[these instructions](https://github.com/mhammond/android-components/blob/autocomplete-places/components/service/sync-places/README.md) + +There's a new `BrowserAutocompleteProvider.kt` which wraps both the +`DomainAutocompleteProvider` and the places component. It first tries to +find matches using places, then using the `DomainAutocompleteProvider`. + +Note that because we don't yet have geckoview hooked up to collect visit +information and write them to the places database. the places database will +be empty - meaning only +suggestions from `DomainAutocompleteProvider` will be shown. However, there's +a trick you can use to import your desktop places database and have it use +that. + +* You will need to check out the application-services repo, and from the + `places` directory (soon to be `components/places`), execute: + + `cargo run --release --example autocomplete -- --import-places auto` + + (alternatively, in the place of `auto`, specify the full path to a desktop + `places.sqlite`) + + This will also start a demo-app where you can perform auto-complete queries. + Press ESC to exit the app, and note that in the same directory you will have + a file `new-places.db` - rename this file to `places.sqlite` + +* Kill the app in the emulator. + +* Use the "Device File Explorer" in Android Studio, and navigate to the + `sdcard/Android/data/org.mozilla.reference.browser/files` directory - if + you've run the app before, you should already find a `places.sqlite` there. + Upload the file created above here and restart the app. + +Note that if you have many many visits (eg, mine has ~170k places with ~230k +visits with a db size of ~75MB) a search for a full domain may take a couple +of seconds, but we know how to fix this. Even with a database this size, +searches of small substrings (eg, a few letters) are fast. We never expect a +"real" mobile device database to be this large, but it's a nice stress-test. diff --git a/app/build.gradle b/app/build.gradle index 115eac98c..cce5c41ad 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,6 +85,8 @@ dependencies { implementation Deps.mozilla_ui_autocomplete + implementation Deps.mozilla_service_sync_places + implementation Deps.mozilla_support_utils systemUniversalImplementation Deps.mozilla_browser_engine_system diff --git a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt new file mode 100644 index 000000000..d99f76497 --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is, roughly, an "auto-complete aggregator" - it first tries to find a match via "places", +// and if that fails, falls back to the "domain" provider. +package org.mozilla.reference.browser.browser + +import android.app.Activity +import android.content.Context +import kotlinx.coroutines.experimental.launch +import mozilla.components.browser.domains.DomainAutoCompleteProvider +import mozilla.components.browser.toolbar.BrowserToolbar +import mozilla.components.support.base.log.Log +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText +import java.net.URL +import org.mozilla.places.PlacesConnection + + +fun debug(message: String) { + Log.log(tag="BrowserAutocomplete", message=message); +} + +class BrowserAutocompleteProvider ( + context: Context, + toolbar: BrowserToolbar, + sessionId: String? = null) { + + private val domainAutoCompleteProvider = DomainAutoCompleteProvider().apply { + initialize(context) + } + + // XXX - this should be in the "profile" dir, but I'm not sure how to fetch that. + private val placesConnection = PlacesConnection(context.getExternalFilesDir(null).absolutePath + "/places.sqlite", "") + + init { + toolbar.setAutocompleteFilter { value, view -> + view?.let { _ -> + // use a coroutine to do this off the main thread. + launch { + // First try places. + // XXX - given the inlineautocomplete functionality, we eventually want to perform + // an OriginOrUrl search - however, for now, we just perform a "normal" search, + // then iterate the results until we find one that matches at the start. + debug("starting places search for $value") + val placesResults = placesConnection.queryAutocomplete(value) + debug("places search for $value, gave ${placesResults.size} matches") + + // Iterate all results and find something that matches at the start. + var finalResult: String? = null + var finalSource: String? = null + var finalSize: Int? = 0; + for (r in placesResults) { + var h = URL(r.url).host; + if (h.startsWith("www.")) { + h = h.substring(4) + } + if (h.startsWith(value)) { + debug("places found matching result $h") + finalResult = h + finalSource = "places" + finalSize = placesResults.size + } + } + if (finalResult == null) { + debug("places found no matching result - trying domain provider") + val result = domainAutoCompleteProvider.autocomplete(value) + finalResult = result.text + finalSource = result.source + finalSize = result.size + } + // domainAutoCompleteProvider always provides a result, even if it is empty strings. + val activity = view.context as Activity + activity.runOnUiThread { + view.applyAutocompleteResult( + InlineAutocompleteEditText.AutocompleteResult(finalResult, finalSource!!, finalSize!!) + ) + } + } + } + } + } +} diff --git a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt index 86c67197e..9274526eb 100644 --- a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.reference.browser.browser +import android.app.Activity import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater @@ -40,6 +41,8 @@ class BrowserFragment : Fragment(), BackHandler { lifecycle.addObserver(ToolbarIntegration(requireContext(), toolbar, sessionId)) tabsToolbarFeature = TabsToolbarFeature(context!!, toolbar, ::showTabs) + + BrowserAutocompleteProvider(context!!, toolbar) } private fun showTabs() { diff --git a/build.gradle b/build.gradle index 5f84255b0..05e4d84e4 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,8 @@ plugins { allprojects { repositories { + mavenLocal() + google() maven { diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 5752fc408..67da87c8f 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -47,6 +47,8 @@ object Deps { const val mozilla_ui_autocomplete = "org.mozilla.components:ui-autocomplete:${Versions.mozilla_android_components}" const val mozilla_support_utils = "org.mozilla.components:support-utils:${Versions.mozilla_android_components}" + const val mozilla_service_sync_places = "org.mozilla.components:sync-places:0.28.0-SNAPSHOT" // TODO: use a released version. + const val testing_junit = "junit:junit:${Versions.junit}" const val testing_robolectric = "org.robolectric:robolectric:${Versions.robolectric}" const val testing_mockito = "org.mockito:mockito-core:${Versions.mockito}" From fcc6325410d9477892c01e2ec425f80dfa1a4957 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Wed, 31 Oct 2018 17:58:22 +1100 Subject: [PATCH 2/2] Update to work with https://github.com/mozilla-mobile/android-components/pull/1240 --- .../browser/BrowserAutocompleteProvider.kt | 39 ++++++------------- buildSrc/src/main/java/Dependencies.kt | 4 +- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt index d99f76497..e7578575e 100644 --- a/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt +++ b/app/src/main/java/org/mozilla/reference/browser/browser/BrowserAutocompleteProvider.kt @@ -11,16 +11,11 @@ import android.content.Context import kotlinx.coroutines.experimental.launch import mozilla.components.browser.domains.DomainAutoCompleteProvider import mozilla.components.browser.toolbar.BrowserToolbar -import mozilla.components.support.base.log.Log import mozilla.components.ui.autocomplete.InlineAutocompleteEditText -import java.net.URL -import org.mozilla.places.PlacesConnection - - -fun debug(message: String) { - Log.log(tag="BrowserAutocomplete", message=message); -} +import mozilla.components.service.sync.places.PlacesAwesomeBarProvider +// NOTE: this should be updated or replaced once mozilla.components.concept.awesomebar is in-place +// and has an implementation. Consider this a proof-of-concept only. class BrowserAutocompleteProvider ( context: Context, toolbar: BrowserToolbar, @@ -31,7 +26,7 @@ class BrowserAutocompleteProvider ( } // XXX - this should be in the "profile" dir, but I'm not sure how to fetch that. - private val placesConnection = PlacesConnection(context.getExternalFilesDir(null).absolutePath + "/places.sqlite", "") + private val placesProvider = PlacesAwesomeBarProvider(context) init { toolbar.setAutocompleteFilter { value, view -> @@ -42,28 +37,16 @@ class BrowserAutocompleteProvider ( // XXX - given the inlineautocomplete functionality, we eventually want to perform // an OriginOrUrl search - however, for now, we just perform a "normal" search, // then iterate the results until we find one that matches at the start. - debug("starting places search for $value") - val placesResults = placesConnection.queryAutocomplete(value) - debug("places search for $value, gave ${placesResults.size} matches") - - // Iterate all results and find something that matches at the start. var finalResult: String? = null var finalSource: String? = null var finalSize: Int? = 0; - for (r in placesResults) { - var h = URL(r.url).host; - if (h.startsWith("www.")) { - h = h.substring(4) - } - if (h.startsWith(value)) { - debug("places found matching result $h") - finalResult = h - finalSource = "places" - finalSize = placesResults.size - } - } - if (finalResult == null) { - debug("places found no matching result - trying domain provider") + + val placesResult = placesProvider.getSuggestion(value) + if (placesResult != null) { + finalResult = placesResult + finalSource = "places" + finalSize = 1 + } else { val result = domainAutoCompleteProvider.autocomplete(value) finalResult = result.text finalSource = result.source diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 137cd4e6c..fdc6b73e0 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -17,7 +17,7 @@ private object Versions { const val android_gradle_plugin = "3.1.4" - const val mozilla_android_components = "0.28.0" + const val mozilla_android_components = "0.28.0-SNAPSHOT" } // Synchronized dependencies used by (some) modules @@ -50,7 +50,7 @@ object Deps { const val mozilla_support_utils = "org.mozilla.components:support-utils:${Versions.mozilla_android_components}" const val mozilla_support_ktx= "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}" - const val mozilla_service_sync_places = "org.mozilla.components:sync-places:0.28.0-SNAPSHOT" // TODO: use a released version. + const val mozilla_service_sync_places = "org.mozilla.components:service-sync-places:0.28.0-SNAPSHOT" // TODO: use a released version. const val testing_junit = "junit:junit:${Versions.junit}" const val testing_robolectric = "org.robolectric:robolectric:${Versions.robolectric}"