summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:19:37 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:19:37 +0000
commitdf790e56c8f7d2a45d612b1eb6409c2e3fd4eeb6 (patch)
treea729ed1d2538977e1f4007efefbf842cf61df5c0
parent0831985355c5aaf222a76402a082498ba52c0f7d (diff)
parent1c5d34d5176f212b7abc0192fc724f88c75de622 (diff)
downloadQuickSearchBox-android14-mainline-sdkext-release.tar.gz
Snap for 10453563 from 1c5d34d5176f212b7abc0192fc724f88c75de622 to mainline-sdkext-releaseaml_sdk_341510000aml_sdk_341410000aml_sdk_341110080aml_sdk_341110000aml_sdk_341010000aml_sdk_340912010android14-mainline-sdkext-release
Change-Id: I822e2a27d322258896fa55777c82b51313013aaf
-rw-r--r--Android.bp18
-rw-r--r--BUILD24
-rw-r--r--OWNERS2
-rw-r--r--src/com/android/quicksearchbox/AbstractInternalSource.java77
-rw-r--r--src/com/android/quicksearchbox/AbstractInternalSource.kt66
-rw-r--r--src/com/android/quicksearchbox/AbstractSource.java133
-rw-r--r--src/com/android/quicksearchbox/AbstractSource.kt132
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java33
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.kt20
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionExtras.java59
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionExtras.kt50
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionWrapper.java106
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt62
-rw-r--r--src/com/android/quicksearchbox/CachingIconLoader.java140
-rw-r--r--src/com/android/quicksearchbox/CachingIconLoader.kt128
-rw-r--r--src/com/android/quicksearchbox/Config.java299
-rw-r--r--src/com/android/quicksearchbox/Config.kt237
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSourceResult.java77
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSourceResult.kt57
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java301
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSuggestionCursor.kt267
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSuggestionExtras.java111
-rw-r--r--src/com/android/quicksearchbox/CursorBackedSuggestionExtras.kt114
-rw-r--r--src/com/android/quicksearchbox/DialogActivity.java69
-rw-r--r--src/com/android/quicksearchbox/DialogActivity.kt57
-rw-r--r--src/com/android/quicksearchbox/EventLogLogger.java111
-rw-r--r--src/com/android/quicksearchbox/EventLogLogger.kt102
-rw-r--r--src/com/android/quicksearchbox/Help.java61
-rw-r--r--src/com/android/quicksearchbox/Help.kt55
-rw-r--r--src/com/android/quicksearchbox/IconLoader.java56
-rw-r--r--src/com/android/quicksearchbox/IconLoader.kt44
-rw-r--r--src/com/android/quicksearchbox/JsonBackedSuggestionExtras.java80
-rw-r--r--src/com/android/quicksearchbox/JsonBackedSuggestionExtras.kt71
-rw-r--r--src/com/android/quicksearchbox/LatencyTracker.java55
-rw-r--r--src/com/android/quicksearchbox/LatencyTracker.kt45
-rw-r--r--src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.java125
-rw-r--r--src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.kt121
-rw-r--r--src/com/android/quicksearchbox/ListSuggestionCursor.java180
-rw-r--r--src/com/android/quicksearchbox/ListSuggestionCursor.kt164
-rw-r--r--src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java50
-rw-r--r--src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.kt46
-rw-r--r--src/com/android/quicksearchbox/Logger.java78
-rw-r--r--src/com/android/quicksearchbox/Logger.kt70
-rw-r--r--src/com/android/quicksearchbox/PackageIconLoader.java261
-rw-r--r--src/com/android/quicksearchbox/PackageIconLoader.kt263
-rw-r--r--src/com/android/quicksearchbox/QsbApplication.java364
-rw-r--r--src/com/android/quicksearchbox/QsbApplication.kt352
-rw-r--r--src/com/android/quicksearchbox/QsbApplicationWrapper.java46
-rw-r--r--src/com/android/quicksearchbox/QsbApplicationWrapper.kt46
-rw-r--r--src/com/android/quicksearchbox/QueryTask.java86
-rw-r--r--src/com/android/quicksearchbox/QueryTask.kt86
-rw-r--r--src/com/android/quicksearchbox/ResultFilter.kt23
-rw-r--r--src/com/android/quicksearchbox/SearchActivity.java510
-rw-r--r--src/com/android/quicksearchbox/SearchActivity.kt475
-rw-r--r--src/com/android/quicksearchbox/SearchSettings.java55
-rw-r--r--src/com/android/quicksearchbox/SearchSettings.kt41
-rw-r--r--src/com/android/quicksearchbox/SearchSettingsImpl.java212
-rw-r--r--src/com/android/quicksearchbox/SearchSettingsImpl.kt185
-rw-r--r--src/com/android/quicksearchbox/SearchWidgetProvider.java187
-rw-r--r--src/com/android/quicksearchbox/SearchWidgetProvider.kt154
-rw-r--r--src/com/android/quicksearchbox/Source.java149
-rw-r--r--src/com/android/quicksearchbox/Source.kt122
-rw-r--r--src/com/android/quicksearchbox/SourceResult.kt (renamed from src/com/android/quicksearchbox/SourceResult.java)15
-rw-r--r--src/com/android/quicksearchbox/Suggestion.java129
-rw-r--r--src/com/android/quicksearchbox/Suggestion.kt93
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursor.java86
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursor.kt71
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorBackedCursor.java200
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorBackedCursor.kt176
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorProvider.java39
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorProvider.kt34
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorWrapper.java84
-rw-r--r--src/com/android/quicksearchbox/SuggestionCursorWrapper.kt63
-rw-r--r--src/com/android/quicksearchbox/SuggestionData.java346
-rw-r--r--src/com/android/quicksearchbox/SuggestionData.kt260
-rw-r--r--src/com/android/quicksearchbox/SuggestionExtras.java42
-rw-r--r--src/com/android/quicksearchbox/SuggestionExtras.kt (renamed from src/com/android/quicksearchbox/SuggestionNonFormatter.java)26
-rw-r--r--src/com/android/quicksearchbox/SuggestionFilter.java29
-rw-r--r--src/com/android/quicksearchbox/SuggestionFilter.kt27
-rw-r--r--src/com/android/quicksearchbox/SuggestionFormatter.java58
-rw-r--r--src/com/android/quicksearchbox/SuggestionFormatter.kt49
-rw-r--r--src/com/android/quicksearchbox/SuggestionNonFormatter.kt (renamed from src/com/android/quicksearchbox/ResultFilter.java)23
-rw-r--r--src/com/android/quicksearchbox/SuggestionPosition.java61
-rw-r--r--src/com/android/quicksearchbox/SuggestionPosition.kt35
-rw-r--r--src/com/android/quicksearchbox/SuggestionUtils.java122
-rw-r--r--src/com/android/quicksearchbox/SuggestionUtils.kt113
-rw-r--r--src/com/android/quicksearchbox/Suggestions.java193
-rw-r--r--src/com/android/quicksearchbox/Suggestions.kt173
-rw-r--r--src/com/android/quicksearchbox/SuggestionsProvider.java34
-rw-r--r--src/com/android/quicksearchbox/SuggestionsProvider.kt28
-rw-r--r--src/com/android/quicksearchbox/SuggestionsProviderImpl.java115
-rw-r--r--src/com/android/quicksearchbox/SuggestionsProviderImpl.kt98
-rw-r--r--src/com/android/quicksearchbox/TextAppearanceFactory.java44
-rw-r--r--src/com/android/quicksearchbox/TextAppearanceFactory.kt35
-rw-r--r--src/com/android/quicksearchbox/VoiceSearch.java106
-rw-r--r--src/com/android/quicksearchbox/VoiceSearch.kt106
-rw-r--r--src/com/android/quicksearchbox/google/AbstractGoogleSource.java124
-rw-r--r--src/com/android/quicksearchbox/google/AbstractGoogleSource.kt109
-rw-r--r--src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.java153
-rw-r--r--src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.kt94
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSearch.java169
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSearch.kt162
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSource.java39
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSource.kt31
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSuggestClient.java218
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSuggestClient.kt210
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSuggestionProvider.java136
-rw-r--r--src/com/android/quicksearchbox/google/GoogleSuggestionProvider.kt153
-rw-r--r--src/com/android/quicksearchbox/google/SearchBaseUrlHelper.java176
-rw-r--r--src/com/android/quicksearchbox/google/SearchBaseUrlHelper.kt169
-rw-r--r--src/com/android/quicksearchbox/ui/BaseSuggestionView.java116
-rw-r--r--src/com/android/quicksearchbox/ui/BaseSuggestionView.kt104
-rw-r--r--src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.java70
-rw-r--r--src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.kt77
-rw-r--r--src/com/android/quicksearchbox/ui/ContactBadge.java56
-rw-r--r--src/com/android/quicksearchbox/ui/ContactBadge.kt52
-rw-r--r--src/com/android/quicksearchbox/ui/CorpusView.java95
-rw-r--r--src/com/android/quicksearchbox/ui/CorpusView.kt84
-rw-r--r--src/com/android/quicksearchbox/ui/DefaultSuggestionView.java254
-rw-r--r--src/com/android/quicksearchbox/ui/DefaultSuggestionView.kt259
-rw-r--r--src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.java89
-rw-r--r--src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.kt84
-rw-r--r--src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java172
-rw-r--r--src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt149
-rw-r--r--src/com/android/quicksearchbox/ui/QueryTextView.java104
-rw-r--r--src/com/android/quicksearchbox/ui/QueryTextView.kt98
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityView.java563
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityView.kt509
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.java59
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt43
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionClickListener.java41
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionClickListener.kt38
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionView.java38
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionView.kt33
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionViewFactory.java63
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionViewFactory.kt59
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionViewInflater.java83
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt80
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsAdapter.java86
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsAdapter.kt68
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.java250
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt221
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListAdapter.java101
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt96
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListView.java61
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListView.kt44
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsView.java76
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsView.kt67
-rw-r--r--src/com/android/quicksearchbox/ui/WebSearchSuggestionView.java102
-rw-r--r--src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt100
-rw-r--r--src/com/android/quicksearchbox/util/AsyncDataSetObservable.java66
-rw-r--r--src/com/android/quicksearchbox/util/AsyncDataSetObservable.kt60
-rw-r--r--src/com/android/quicksearchbox/util/BarrierConsumer.java94
-rw-r--r--src/com/android/quicksearchbox/util/BarrierConsumer.kt89
-rw-r--r--src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.java93
-rw-r--r--src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.kt76
-rw-r--r--src/com/android/quicksearchbox/util/CachedLater.java139
-rw-r--r--src/com/android/quicksearchbox/util/CachedLater.kt129
-rw-r--r--src/com/android/quicksearchbox/util/Consumer.kt (renamed from src/com/android/quicksearchbox/util/Consumer.java)25
-rw-r--r--src/com/android/quicksearchbox/util/Consumers.java84
-rw-r--r--src/com/android/quicksearchbox/util/Consumers.kt87
-rw-r--r--src/com/android/quicksearchbox/util/Factory.kt (renamed from src/com/android/quicksearchbox/util/Factory.java)11
-rw-r--r--src/com/android/quicksearchbox/util/HttpHelper.java152
-rw-r--r--src/com/android/quicksearchbox/util/HttpHelper.kt91
-rw-r--r--src/com/android/quicksearchbox/util/JavaNetHttpHelper.java187
-rw-r--r--src/com/android/quicksearchbox/util/JavaNetHttpHelper.kt186
-rw-r--r--src/com/android/quicksearchbox/util/LevenshteinDistance.java194
-rw-r--r--src/com/android/quicksearchbox/util/LevenshteinDistance.kt165
-rw-r--r--src/com/android/quicksearchbox/util/NamedTask.kt (renamed from src/com/android/quicksearchbox/util/NamedTask.java)15
-rw-r--r--src/com/android/quicksearchbox/util/NamedTaskExecutor.java44
-rw-r--r--src/com/android/quicksearchbox/util/NamedTaskExecutor.kt35
-rw-r--r--src/com/android/quicksearchbox/util/NoOpConsumer.java30
-rw-r--r--src/com/android/quicksearchbox/util/NoOpConsumer.kt25
-rw-r--r--src/com/android/quicksearchbox/util/Now.java41
-rw-r--r--src/com/android/quicksearchbox/util/Now.kt28
-rw-r--r--src/com/android/quicksearchbox/util/NowOrLater.kt (renamed from src/com/android/quicksearchbox/util/NowOrLater.java)38
-rw-r--r--src/com/android/quicksearchbox/util/NowOrLaterWrapper.java51
-rw-r--r--src/com/android/quicksearchbox/util/NowOrLaterWrapper.kt44
-rw-r--r--src/com/android/quicksearchbox/util/PerNameExecutor.java64
-rw-r--r--src/com/android/quicksearchbox/util/PerNameExecutor.kt58
-rw-r--r--src/com/android/quicksearchbox/util/PriorityThreadFactory.java50
-rw-r--r--src/com/android/quicksearchbox/util/PriorityThreadFactory.kt37
-rw-r--r--src/com/android/quicksearchbox/util/QuietlyCloseable.kt (renamed from src/com/android/quicksearchbox/util/QuietlyCloseable.java)17
-rw-r--r--src/com/android/quicksearchbox/util/SQLiteAsyncQuery.java43
-rw-r--r--src/com/android/quicksearchbox/util/SQLiteAsyncQuery.kt40
-rw-r--r--src/com/android/quicksearchbox/util/SQLiteTransaction.java48
-rw-r--r--src/com/android/quicksearchbox/util/SQLiteTransaction.kt45
-rw-r--r--src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.java102
-rw-r--r--src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.kt99
-rw-r--r--src/com/android/quicksearchbox/util/Util.java90
-rw-r--r--src/com/android/quicksearchbox/util/Util.kt85
-rw-r--r--tests/src/com/android/quicksearchbox/tests/CrashingIconProvider.java7
192 files changed, 9674 insertions, 10960 deletions
diff --git a/Android.bp b/Android.bp
index 6f26bf0..cf84788 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,6 +1,4 @@
-//
// Copyright (C) 2009 The Android Open Source Project
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -22,18 +20,22 @@ package {
android_app {
name: "QuickSearchBox",
sdk_version: "current",
- static_libs: [
- "guava",
- "android-common",
- ],
srcs: [
- "src/**/*.java",
+ "src/**/*.kt",
"src/**/*.logtags",
],
+ static_libs: [
+ "guava",
+ "android-common",
+ "androidx.core_core",
+ "kotlinx_coroutines",
+ ],
certificate: "shared",
product_specific: true,
resource_dirs: ["res"],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
-}
+
+ kotlincflags: ["-Werror"],
+} \ No newline at end of file
diff --git a/BUILD b/BUILD
deleted file mode 100644
index 76b0c0c..0000000
--- a/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-load("@rules_android//rules:rules.bzl", "android_binary")
-load("//build/make/tools:event_log_tags.bzl", "event_log_tags")
-
-event_log_tags(
- name = "genlogtags",
- srcs = glob(["src/**/*.logtags"]),
-)
-
-android_binary(
- name = "QuickSearchBox",
- srcs = glob(["src/**/*.java"]) + [
- ":genlogtags",
- ],
- custom_package = "com.android.quicksearchbox",
- javacopts = ["-Xep:ArrayToString:OFF"],
- manifest = "AndroidManifest.xml",
- # TODO(182591919): uncomment the below once android rules are integrated with r8.
- # proguard_specs = ["proguard.flags"],
- resource_files = glob(["res/**"]),
- deps = [
- "//external/guava",
- "//frameworks/ex/common:android-common",
- ],
-)
diff --git a/OWNERS b/OWNERS
index ed1d60a..8bf72f2 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,5 @@
# Default code reviewers picked from top 3 or more developers.
# Please update this list if you find better candidates.
rtenneti@google.com
+amithds@google.com
+iankaz@google.com \ No newline at end of file
diff --git a/src/com/android/quicksearchbox/AbstractInternalSource.java b/src/com/android/quicksearchbox/AbstractInternalSource.java
deleted file mode 100644
index 5567452..0000000
--- a/src/com/android/quicksearchbox/AbstractInternalSource.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-
-/**
- * Abstract implementation of a source that is not backed by a searchable activity.
- */
-public abstract class AbstractInternalSource extends AbstractSource {
-
- public AbstractInternalSource(Context context, Handler uiThread, NamedTaskExecutor iconLoader) {
- super(context, uiThread, iconLoader);
- }
-
- @Override
- public String getSuggestUri() {
- return null;
- }
-
- @Override
- public boolean canRead() {
- return true;
- }
-
- @Override
- public String getDefaultIntentData() {
- return null;
- }
-
- @Override
- protected String getIconPackage() {
- return getContext().getPackageName();
- }
-
- @Override
- public int getQueryThreshold() {
- return 0;
- }
-
- @Override
- public Drawable getSourceIcon() {
- return getContext().getResources().getDrawable(getSourceIconResource());
- }
-
- @Override
- public Uri getSourceIconUri() {
- return Uri.parse("android.resource://" + getContext().getPackageName()
- + "/" + getSourceIconResource());
- }
-
- protected abstract int getSourceIconResource();
-
- @Override
- public boolean queryAfterZeroResults() {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/AbstractInternalSource.kt b/src/com/android/quicksearchbox/AbstractInternalSource.kt
new file mode 100644
index 0000000..3e142ad
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractInternalSource.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import com.android.quicksearchbox.util.NamedTaskExecutor
+
+/** Abstract implementation of a source that is not backed by a searchable activity. */
+abstract class AbstractInternalSource(
+ context: Context?,
+ uiThread: Handler?,
+ iconLoader: NamedTaskExecutor
+) : AbstractSource(context, uiThread, iconLoader) {
+ @get:Override
+ override val suggestUri: String?
+ get() = null
+
+ @Override
+ override fun canRead(): Boolean {
+ return true
+ }
+
+ override val defaultIntentData: String?
+ get() = null
+
+ @get:Override
+ override val iconPackage: String
+ get() = context!!.getPackageName()
+
+ @get:Override
+ override val queryThreshold: Int
+ get() = 0
+
+ @get:Override
+ override val sourceIcon: Drawable
+ get() = context?.getResources()!!.getDrawable(sourceIconResource, null)
+
+ @get:Override
+ override val sourceIconUri: Uri
+ get() =
+ Uri.parse(
+ "android.resource://" + context!!.getPackageName().toString() + "/" + sourceIconResource
+ )
+ protected abstract val sourceIconResource: Int
+
+ @Override
+ override fun queryAfterZeroResults(): Boolean {
+ return true
+ }
+}
diff --git a/src/com/android/quicksearchbox/AbstractSource.java b/src/com/android/quicksearchbox/AbstractSource.java
deleted file mode 100644
index f8c6d0c..0000000
--- a/src/com/android/quicksearchbox/AbstractSource.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-import com.android.quicksearchbox.util.NowOrLater;
-
-import android.app.SearchManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * Abstract suggestion source implementation.
- */
-public abstract class AbstractSource implements Source {
-
- private static final String TAG = "QSB.AbstractSource";
-
- private final Context mContext;
- private final Handler mUiThread;
-
- private IconLoader mIconLoader;
-
- private final NamedTaskExecutor mIconLoaderExecutor;
-
- public AbstractSource(Context context, Handler uiThread, NamedTaskExecutor iconLoader) {
- mContext = context;
- mUiThread = uiThread;
- mIconLoaderExecutor = iconLoader;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected IconLoader getIconLoader() {
- if (mIconLoader == null) {
- String iconPackage = getIconPackage();
- mIconLoader = new CachingIconLoader(
- new PackageIconLoader(mContext, iconPackage, mUiThread, mIconLoaderExecutor));
- }
- return mIconLoader;
- }
-
- protected abstract String getIconPackage();
-
- @Override
- public NowOrLater<Drawable> getIcon(String drawableId) {
- return getIconLoader().getIcon(drawableId);
- }
-
- @Override
- public Uri getIconUri(String drawableId) {
- return getIconLoader().getIconUri(drawableId);
- }
-
- @Override
- public Intent createSearchIntent(String query, Bundle appData) {
- return createSourceSearchIntent(getIntentComponent(), query, appData);
- }
-
- public static Intent createSourceSearchIntent(ComponentName activity, String query,
- Bundle appData) {
- if (activity == null) {
- Log.w(TAG, "Tried to create search intent with no target activity");
- return null;
- }
- Intent intent = new Intent(Intent.ACTION_SEARCH);
- intent.setComponent(activity);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want.
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(SearchManager.USER_QUERY, query);
- intent.putExtra(SearchManager.QUERY, query);
- if (appData != null) {
- intent.putExtra(SearchManager.APP_DATA, appData);
- }
- return intent;
- }
-
- protected Intent createVoiceWebSearchIntent(Bundle appData) {
- return QsbApplication.get(mContext).getVoiceSearch()
- .createVoiceWebSearchIntent(appData);
- }
-
- @Override
- public Source getRoot() {
- return this;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o != null && o instanceof Source) {
- Source s = ((Source) o).getRoot();
- if (s.getClass().equals(this.getClass())) {
- return s.getName().equals(getName());
- }
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return getName().hashCode();
- }
-
- @Override
- public String toString() {
- return "Source{name=" + getName() + "}";
- }
-
-}
diff --git a/src/com/android/quicksearchbox/AbstractSource.kt b/src/com/android/quicksearchbox/AbstractSource.kt
new file mode 100644
index 0000000..a10f1bd
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractSource.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.util.Log
+import com.android.quicksearchbox.util.NamedTaskExecutor
+import com.android.quicksearchbox.util.NowOrLater
+
+/** Abstract suggestion source implementation. */
+abstract class AbstractSource(
+ context: Context?,
+ uiThread: Handler?,
+ iconLoader: NamedTaskExecutor
+) : Source {
+ private val mContext: Context?
+ private val mUiThread: Handler?
+ private var mIconLoader: IconLoader? = null
+ private val mIconLoaderExecutor: NamedTaskExecutor
+ protected val context: Context?
+ get() = mContext
+ protected val iconLoader: IconLoader?
+ get() {
+ if (mIconLoader == null) {
+ val iconPackage = iconPackage
+ mIconLoader =
+ CachingIconLoader(
+ PackageIconLoader(mContext, iconPackage, mUiThread, mIconLoaderExecutor)
+ )
+ }
+ return mIconLoader
+ }
+ protected abstract val iconPackage: String
+
+ @Override
+ override fun getIcon(drawableId: String?): NowOrLater<Drawable?>? {
+ return iconLoader?.getIcon(drawableId)
+ }
+
+ @Override
+ override fun getIconUri(drawableId: String?): Uri? {
+ return iconLoader?.getIconUri(drawableId)
+ }
+
+ @Override
+ override fun createSearchIntent(query: String?, appData: Bundle?): Intent? {
+ return createSourceSearchIntent(intentComponent, query, appData)
+ }
+
+ protected fun createVoiceWebSearchIntent(appData: Bundle?): Intent? {
+ return QsbApplication.get(mContext).voiceSearch?.createVoiceWebSearchIntent(appData)
+ }
+
+ override fun getRoot(): Source {
+ return this
+ }
+
+ @Override
+ override fun equals(other: Any?): Boolean {
+ if (other is Source) {
+ val s: Source = other.getRoot()
+ if (s::class == this::class) {
+ return s.name.equals(name)
+ }
+ }
+ return false
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ return name.hashCode()
+ }
+
+ @Override
+ override fun toString(): String {
+ return "Source{name=" + name.toString() + "}"
+ }
+
+ companion object {
+ private const val TAG = "QSB.AbstractSource"
+
+ @JvmStatic
+ fun createSourceSearchIntent(
+ activity: ComponentName?,
+ query: String?,
+ appData: Bundle?
+ ): Intent? {
+ if (activity == null) {
+ Log.w(TAG, "Tried to create search intent with no target activity")
+ return null
+ }
+ val intent = Intent(Intent.ACTION_SEARCH)
+ intent.setComponent(activity)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // We need CLEAR_TOP to avoid reusing an old task that has other activities
+ // on top of the one we want.
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ intent.putExtra(SearchManager.USER_QUERY, query)
+ intent.putExtra(SearchManager.QUERY, query)
+ if (appData != null) {
+ intent.putExtra(SearchManager.APP_DATA, appData)
+ }
+ return intent
+ }
+ }
+
+ init {
+ mContext = context
+ mUiThread = uiThread
+ mIconLoaderExecutor = iconLoader
+ }
+}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java b/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java
deleted file mode 100644
index 78fc1c2..0000000
--- a/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-/**
- * A SuggestionCursor that delegates all calls to other suggestions.
- */
-public abstract class AbstractSuggestionCursorWrapper extends AbstractSuggestionWrapper
- implements SuggestionCursor {
-
- private final String mUserQuery;
-
- public AbstractSuggestionCursorWrapper(String userQuery) {
- mUserQuery = userQuery;
- }
-
- public String getUserQuery() {
- return mUserQuery;
- }
-}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.kt b/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.kt
new file mode 100644
index 0000000..5e00434
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** A SuggestionCursor that delegates all calls to other suggestions. */
+abstract class AbstractSuggestionCursorWrapper(override val userQuery: String) :
+ AbstractSuggestionWrapper(), SuggestionCursor
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionExtras.java b/src/com/android/quicksearchbox/AbstractSuggestionExtras.java
deleted file mode 100644
index f2c5690..0000000
--- a/src/com/android/quicksearchbox/AbstractSuggestionExtras.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import org.json.JSONException;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * Abstract SuggestionExtras supporting flattening to JSON.
- */
-public abstract class AbstractSuggestionExtras implements SuggestionExtras {
-
- private final SuggestionExtras mMore;
-
- protected AbstractSuggestionExtras(SuggestionExtras more) {
- mMore = more;
- }
-
- public Collection<String> getExtraColumnNames() {
- HashSet<String> columns = new HashSet<String>();
- columns.addAll(doGetExtraColumnNames());
- if (mMore != null) {
- columns.addAll(mMore.getExtraColumnNames());
- }
- return columns;
- }
-
- protected abstract Collection<String> doGetExtraColumnNames();
-
- public String getExtra(String columnName) {
- String extra = doGetExtra(columnName);
- if (extra == null && mMore != null) {
- extra = mMore.getExtra(columnName);
- }
- return extra;
- }
-
- protected abstract String doGetExtra(String columnName);
-
- public String toJsonString() throws JSONException {
- return new JsonBackedSuggestionExtras(this).toString();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionExtras.kt b/src/com/android/quicksearchbox/AbstractSuggestionExtras.kt
new file mode 100644
index 0000000..04905cb
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractSuggestionExtras.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import kotlin.collections.HashSet
+import org.json.JSONException
+
+/** Abstract SuggestionExtras supporting flattening to JSON. */
+abstract class AbstractSuggestionExtras
+protected constructor(private val mMore: SuggestionExtras?) : SuggestionExtras {
+ @get:Override
+ override val extraColumnNames: Collection<String>
+ get() {
+ val columns: HashSet<String> = HashSet<String>()
+ columns.addAll(doGetExtraColumnNames())
+ if (mMore != null) {
+ columns.addAll(mMore.extraColumnNames)
+ }
+ return columns
+ }
+
+ protected abstract fun doGetExtraColumnNames(): Collection<String>
+ override fun getExtra(columnName: String?): String? {
+ var extra = doGetExtra(columnName)
+ if (extra == null && mMore != null) {
+ extra = mMore.getExtra(columnName)
+ }
+ return extra
+ }
+
+ protected abstract fun doGetExtra(columnName: String?): String?
+
+ @Throws(JSONException::class)
+ override fun toJsonString(): String? {
+ return JsonBackedSuggestionExtras(this).toString()
+ }
+}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionWrapper.java b/src/com/android/quicksearchbox/AbstractSuggestionWrapper.java
deleted file mode 100644
index a8e4f2b..0000000
--- a/src/com/android/quicksearchbox/AbstractSuggestionWrapper.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.content.ComponentName;
-
-/**
- * A Suggestion that delegates all calls to other suggestions.
- */
-public abstract class AbstractSuggestionWrapper implements Suggestion {
-
- /**
- * Gets the current suggestion.
- */
- protected abstract Suggestion current();
-
- public String getShortcutId() {
- return current().getShortcutId();
- }
-
- public String getSuggestionFormat() {
- return current().getSuggestionFormat();
- }
-
- public String getSuggestionIcon1() {
- return current().getSuggestionIcon1();
- }
-
- public String getSuggestionIcon2() {
- return current().getSuggestionIcon2();
- }
-
- public String getSuggestionIntentAction() {
- return current().getSuggestionIntentAction();
- }
-
- public ComponentName getSuggestionIntentComponent() {
- return current().getSuggestionIntentComponent();
- }
-
- public String getSuggestionIntentDataString() {
- return current().getSuggestionIntentDataString();
- }
-
- public String getSuggestionIntentExtraData() {
- return current().getSuggestionIntentExtraData();
- }
-
- public String getSuggestionLogType() {
- return current().getSuggestionLogType();
- }
-
- public String getSuggestionQuery() {
- return current().getSuggestionQuery();
- }
-
- public Source getSuggestionSource() {
- return current().getSuggestionSource();
- }
-
- public String getSuggestionText1() {
- return current().getSuggestionText1();
- }
-
- public String getSuggestionText2() {
- return current().getSuggestionText2();
- }
-
- public String getSuggestionText2Url() {
- return current().getSuggestionText2Url();
- }
-
- public boolean isSpinnerWhileRefreshing() {
- return current().isSpinnerWhileRefreshing();
- }
-
- public boolean isSuggestionShortcut() {
- return current().isSuggestionShortcut();
- }
-
- public boolean isWebSearchSuggestion() {
- return current().isWebSearchSuggestion();
- }
-
- public boolean isHistorySuggestion() {
- return current().isHistorySuggestion();
- }
-
- public SuggestionExtras getExtras() {
- return current().getExtras();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt b/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt
new file mode 100644
index 0000000..d57e230
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ComponentName
+
+/** A Suggestion that delegates all calls to other suggestions. */
+abstract class AbstractSuggestionWrapper : Suggestion {
+ /** Gets the current suggestion. */
+ protected abstract fun current(): Suggestion?
+ override val shortcutId: String?
+ get() = current()?.shortcutId
+ override val suggestionFormat: String?
+ get() = current()?.suggestionFormat
+ override val suggestionIcon1: String?
+ get() = current()?.suggestionIcon1
+ override val suggestionIcon2: String?
+ get() = current()?.suggestionIcon2
+ override val suggestionIntentAction: String?
+ get() = current()?.suggestionIntentAction
+ override val suggestionIntentComponent: ComponentName?
+ get() = current()?.suggestionIntentComponent
+ override val suggestionIntentDataString: String?
+ get() = current()?.suggestionIntentDataString
+ override val suggestionIntentExtraData: String?
+ get() = current()?.suggestionIntentExtraData
+ override val suggestionLogType: String?
+ get() = current()?.suggestionLogType
+ override val suggestionQuery: String?
+ get() = current()?.suggestionQuery
+ override val suggestionSource: Source?
+ get() = current()?.suggestionSource
+ override val suggestionText1: String?
+ get() = current()?.suggestionText1
+ override val suggestionText2: String?
+ get() = current()?.suggestionText2
+ override val suggestionText2Url: String?
+ get() = current()?.suggestionText2Url
+ override val isSpinnerWhileRefreshing: Boolean
+ get() = current()?.isSpinnerWhileRefreshing == true
+ override val isSuggestionShortcut: Boolean
+ get() = current()?.isSuggestionShortcut == true
+ override val isWebSearchSuggestion: Boolean
+ get() = current()?.isWebSearchSuggestion == true
+ override val isHistorySuggestion: Boolean
+ get() = current()?.isHistorySuggestion == true
+ override val extras: SuggestionExtras?
+ get() = current()?.extras
+}
diff --git a/src/com/android/quicksearchbox/CachingIconLoader.java b/src/com/android/quicksearchbox/CachingIconLoader.java
deleted file mode 100644
index ea45b40..0000000
--- a/src/com/android/quicksearchbox/CachingIconLoader.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.CachedLater;
-import com.android.quicksearchbox.util.Consumer;
-import com.android.quicksearchbox.util.Now;
-import com.android.quicksearchbox.util.NowOrLater;
-import com.android.quicksearchbox.util.NowOrLaterWrapper;
-
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.WeakHashMap;
-
-/**
- * Icon loader that caches the results of another icon loader.
- *
- */
-public class CachingIconLoader implements IconLoader {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.CachingIconLoader";
-
- private final IconLoader mWrapped;
-
- private final WeakHashMap<String, Entry> mIconCache;
-
- /**
- * Creates a new caching icon loader.
- *
- * @param wrapped IconLoader whose results will be cached.
- */
- public CachingIconLoader(IconLoader wrapped) {
- mWrapped = wrapped;
- mIconCache = new WeakHashMap<String, Entry>();
- }
-
- public NowOrLater<Drawable> getIcon(String drawableId) {
- if (DBG) Log.d(TAG, "getIcon(" + drawableId + ")");
- if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
- return new Now<Drawable>(null);
- }
- Entry newEntry = null;
- NowOrLater<Drawable.ConstantState> drawableState;
- synchronized (this) {
- drawableState = queryCache(drawableId);
- if (drawableState == null) {
- newEntry = new Entry();
- storeInIconCache(drawableId, newEntry);
- }
- }
- if (drawableState != null) {
- return new NowOrLaterWrapper<Drawable.ConstantState, Drawable>(drawableState){
- @Override
- public Drawable get(Drawable.ConstantState value) {
- return value == null ? null : value.newDrawable();
- }};
- }
- NowOrLater<Drawable> drawable = mWrapped.getIcon(drawableId);
- newEntry.set(drawable);
- storeInIconCache(drawableId, newEntry);
- return drawable;
- }
-
- public Uri getIconUri(String drawableId) {
- return mWrapped.getIconUri(drawableId);
- }
-
- private synchronized NowOrLater<Drawable.ConstantState> queryCache(String drawableId) {
- NowOrLater<Drawable.ConstantState> cached = mIconCache.get(drawableId);
- if (DBG) {
- if (cached != null) Log.d(TAG, "Found icon in cache: " + drawableId);
- }
- return cached;
- }
-
- private synchronized void storeInIconCache(String resourceUri, Entry drawable) {
- if (drawable != null) {
- mIconCache.put(resourceUri, drawable);
- }
- }
-
- private static class Entry extends CachedLater<Drawable.ConstantState>
- implements Consumer<Drawable>{
- private NowOrLater<Drawable> mDrawable;
- private boolean mGotDrawable;
- private boolean mCreateRequested;
-
- public Entry() {
- }
-
- public synchronized void set(NowOrLater<Drawable> drawable) {
- if (mGotDrawable) throw new IllegalStateException("set() may only be called once.");
- mGotDrawable = true;
- mDrawable = drawable;
- if (mCreateRequested) {
- getLater();
- }
- }
-
- @Override
- protected synchronized void create() {
- if (!mCreateRequested) {
- mCreateRequested = true;
- if (mGotDrawable) {
- getLater();
- }
- }
- }
-
- private void getLater() {
- NowOrLater<Drawable> drawable = mDrawable;
- mDrawable = null;
- drawable.getLater(this);
- }
-
- public boolean consume(Drawable value) {
- store(value == null ? null : value.getConstantState());
- return true;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/CachingIconLoader.kt b/src/com/android/quicksearchbox/CachingIconLoader.kt
new file mode 100644
index 0000000..afc039b
--- /dev/null
+++ b/src/com/android/quicksearchbox/CachingIconLoader.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.text.TextUtils
+import android.util.Log
+import com.android.quicksearchbox.util.*
+import java.util.WeakHashMap
+
+/** Icon loader that caches the results of another icon loader. */
+class CachingIconLoader(private val mWrapped: IconLoader) : IconLoader {
+ private val mIconCache: WeakHashMap<String, Entry>
+ override fun getIcon(drawableId: String?): NowOrLater<Drawable?>? {
+ if (DBG) Log.d(TAG, "getIcon($drawableId)")
+ if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
+ return Now<Drawable>(null)
+ }
+ var newEntry: Entry? = null
+ var drawableState: NowOrLater<Drawable.ConstantState?>?
+ synchronized(this) {
+ drawableState = queryCache(drawableId)
+ if (drawableState == null) {
+ newEntry = Entry()
+ storeInIconCache(drawableId, newEntry)
+ }
+ }
+ if (drawableState != null) {
+ return object : NowOrLaterWrapper<Drawable.ConstantState?, Drawable?>(drawableState!!) {
+ @Override
+ override operator fun get(value: Drawable.ConstantState?): Drawable? {
+ return if (value == null) null else value.newDrawable()
+ }
+ }
+ }
+ val drawable: NowOrLater<Drawable?>? = mWrapped.getIcon(drawableId)
+ newEntry?.set(drawable)
+ storeInIconCache(drawableId, newEntry)
+ return drawable!!
+ }
+
+ override fun getIconUri(drawableId: String?): Uri? {
+ return mWrapped.getIconUri(drawableId)
+ }
+
+ @Synchronized
+ private fun queryCache(drawableId: String?): NowOrLater<Drawable.ConstantState?>? {
+ val cached: Entry? = mIconCache.get(drawableId)
+ if (DBG) {
+ if (cached != null) Log.d(TAG, "Found icon in cache: $drawableId")
+ }
+ return cached
+ }
+
+ @Synchronized
+ private fun storeInIconCache(resourceUri: String?, drawable: Entry?) {
+ if (drawable != null) {
+ mIconCache.put(resourceUri, drawable)
+ }
+ }
+
+ private class Entry : CachedLater<Drawable.ConstantState?>(), Consumer<Drawable?> {
+ private var mDrawable: NowOrLater<Drawable?>? = null
+ private var mGotDrawable = false
+ private var mCreateRequested = false
+
+ @Synchronized
+ fun set(drawable: NowOrLater<Drawable?>?) {
+ if (mGotDrawable) throw IllegalStateException("set() may only be called once.")
+ mGotDrawable = true
+ mDrawable = drawable
+ if (mCreateRequested) {
+ later
+ }
+ }
+
+ @Override
+ @Synchronized
+ override fun create() {
+ if (!mCreateRequested) {
+ mCreateRequested = true
+ if (mGotDrawable) {
+ later
+ }
+ }
+ }
+
+ private val later: Unit
+ get() {
+ val drawable: NowOrLater<Drawable?>? = mDrawable
+ mDrawable = null
+ drawable!!.getLater(this)
+ }
+
+ override fun consume(value: Drawable?): Boolean {
+ store(if (value == null) null else value.getConstantState())
+ return true
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.CachingIconLoader"
+ }
+
+ /**
+ * Creates a new caching icon loader.
+ *
+ * @param wrapped IconLoader whose results will be cached.
+ */
+ init {
+ mIconCache = WeakHashMap<String, Entry>()
+ }
+}
diff --git a/src/com/android/quicksearchbox/Config.java b/src/com/android/quicksearchbox/Config.java
deleted file mode 100644
index 678d411..0000000
--- a/src/com/android/quicksearchbox/Config.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.AlarmManager;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Process;
-import android.util.Log;
-
-import java.util.HashSet;
-
-/**
- * Provides values for configurable parameters in all of QSB.
- *
- * All the methods in this class return fixed default values. Subclasses may
- * make these values server-side settable.
- *
- */
-public class Config {
-
- private static final String TAG = "QSB.Config";
- private static final boolean DBG = false;
-
- protected static final long SECOND_MILLIS = 1000L;
- protected static final long MINUTE_MILLIS = 60L * SECOND_MILLIS;
- protected static final long DAY_MILLIS = 86400000L;
-
- private static final int NUM_PROMOTED_SOURCES = 3;
- private static final int MAX_RESULTS_PER_SOURCE = 50;
- private static final long SOURCE_TIMEOUT_MILLIS = 10000;
-
- private static final int QUERY_THREAD_PRIORITY =
- Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE;
-
- private static final long MAX_STAT_AGE_MILLIS = 30 * DAY_MILLIS;
- private static final int MIN_CLICKS_FOR_SOURCE_RANKING = 3;
-
- private static final int NUM_WEB_CORPUS_THREADS = 2;
-
- private static final int LATENCY_LOG_FREQUENCY = 1000;
-
- private static final long TYPING_SUGGESTIONS_UPDATE_DELAY_MILLIS = 100;
- private static final long PUBLISH_RESULT_DELAY_MILLIS = 200;
-
- private static final long VOICE_SEARCH_HINT_ACTIVE_PERIOD = 7L * DAY_MILLIS;
-
- private static final long VOICE_SEARCH_HINT_UPDATE_INTERVAL
- = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
-
- private static final long VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS
- = AlarmManager.INTERVAL_HOUR * 2;
-
- private static final long VOICE_SEARCH_HINT_CHANGE_PERIOD = 2L * MINUTE_MILLIS;
-
- private static final long VOICE_SEARCH_HINT_VISIBLE_PERIOD = 6L * MINUTE_MILLIS;
-
- private static final int HTTP_CONNECT_TIMEOUT_MILLIS = 4000;
- private static final int HTTP_READ_TIMEOUT_MILLIS = 4000;
-
- private static final String USER_AGENT = "Android/1.0";
-
- private final Context mContext;
- private HashSet<String> mDefaultCorpora;
- private HashSet<String> mHiddenCorpora;
- private HashSet<String> mDefaultCorporaSuggestUris;
-
- /**
- * Creates a new config that uses hard-coded default values.
- */
- public Config(Context context) {
- mContext = context;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- /**
- * Releases any resources used by the configuration object.
- *
- * Default implementation does nothing.
- */
- public void close() {
- }
-
- private HashSet<String> loadResourceStringSet(int res) {
- HashSet<String> set = new HashSet<String>();
- String[] items = mContext.getResources().getStringArray(res);
- for (String item : items) {
- set.add(item);
- }
- return set;
- }
-
- /**
- * The number of promoted sources.
- */
- public int getNumPromotedSources() {
- return NUM_PROMOTED_SOURCES;
- }
-
- /**
- * The number of suggestions visible above the onscreen keyboard.
- */
- public int getNumSuggestionsAboveKeyboard() {
- // Get the list of default corpora from a resource, which allows vendor overlays.
- return mContext.getResources().getInteger(R.integer.num_suggestions_above_keyboard);
- }
-
- /**
- * The maximum number of suggestions to promote.
- */
- public int getMaxPromotedSuggestions() {
- return mContext.getResources().getInteger(R.integer.max_promoted_suggestions);
- }
-
- public int getMaxPromotedResults() {
- return mContext.getResources().getInteger(R.integer.max_promoted_results);
- }
-
- /**
- * The number of results to ask each source for.
- */
- public int getMaxResultsPerSource() {
- return MAX_RESULTS_PER_SOURCE;
- }
-
- /**
- * The maximum number of shortcuts to show for the web source in All mode.
- */
- public int getMaxShortcutsPerWebSource() {
- return mContext.getResources().getInteger(R.integer.max_shortcuts_per_web_source);
- }
-
- /**
- * The maximum number of shortcuts to show for each non-web source in All mode.
- */
- public int getMaxShortcutsPerNonWebSource() {
- return mContext.getResources().getInteger(R.integer.max_shortcuts_per_non_web_source);
- }
-
- /**
- * Gets the maximum number of shortcuts that will be shown from the given source.
- */
- public int getMaxShortcuts(String sourceName) {
- return getMaxShortcutsPerNonWebSource();
- }
-
- /**
- * The timeout for querying each source, in milliseconds.
- */
- public long getSourceTimeoutMillis() {
- return SOURCE_TIMEOUT_MILLIS;
- }
-
- /**
- * The priority of query threads.
- *
- * @return A thread priority, as defined in {@link Process}.
- */
- public int getQueryThreadPriority() {
- return QUERY_THREAD_PRIORITY;
- }
-
- /**
- * The maximum age of log data used for shortcuts.
- */
- public long getMaxStatAgeMillis(){
- return MAX_STAT_AGE_MILLIS;
- }
-
- /**
- * The minimum number of clicks needed to rank a source.
- */
- public int getMinClicksForSourceRanking(){
- return MIN_CLICKS_FOR_SOURCE_RANKING;
- }
-
- public int getNumWebCorpusThreads() {
- return NUM_WEB_CORPUS_THREADS;
- }
-
- /**
- * How often query latency should be logged.
- *
- * @return An integer in the range 0-1000. 0 means that no latency events
- * should be logged. 1000 means that all latency events should be logged.
- */
- public int getLatencyLogFrequency() {
- return LATENCY_LOG_FREQUENCY;
- }
-
- /**
- * The delay in milliseconds before suggestions are updated while typing.
- * If a new character is typed before this timeout expires, the timeout is reset.
- */
- public long getTypingUpdateSuggestionsDelayMillis() {
- return TYPING_SUGGESTIONS_UPDATE_DELAY_MILLIS;
- }
-
- public boolean allowVoiceSearchHints() {
- return true;
- }
-
- /**
- * The period of time for which after installing voice search we should consider showing voice
- * search hints.
- *
- * @return The period in milliseconds.
- */
- public long getVoiceSearchHintActivePeriod() {
- return VOICE_SEARCH_HINT_ACTIVE_PERIOD;
- }
-
- /**
- * The time interval at which we should consider whether or not to show some voice search hints.
- *
- * @return The period in milliseconds.
- */
- public long getVoiceSearchHintUpdatePeriod() {
- return VOICE_SEARCH_HINT_UPDATE_INTERVAL;
- }
-
- /**
- * The time interval at which, on average, voice search hints are displayed.
- *
- * @return The period in milliseconds.
- */
- public long getVoiceSearchHintShowPeriod() {
- return VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS;
- }
-
- /**
- * The amount of time for which voice search hints are displayed in one go.
- *
- * @return The period in milliseconds.
- */
- public long getVoiceSearchHintVisibleTime() {
- return VOICE_SEARCH_HINT_VISIBLE_PERIOD;
- }
-
- /**
- * The period that we change voice search hints at while they're being displayed.
- *
- * @return The period in milliseconds.
- */
- public long getVoiceSearchHintChangePeriod() {
- return VOICE_SEARCH_HINT_CHANGE_PERIOD;
- }
-
- public boolean showSuggestionsForZeroQuery() {
- // Get the list of default corpora from a resource, which allows vendor overlays.
- return mContext.getResources().getBoolean(R.bool.show_zero_query_suggestions);
- }
-
- public boolean showShortcutsForZeroQuery() {
- // Get the list of default corpora from a resource, which allows vendor overlays.
- return mContext.getResources().getBoolean(R.bool.show_zero_query_shortcuts);
- }
-
- public boolean showScrollingSuggestions() {
- return mContext.getResources().getBoolean(R.bool.show_scrolling_suggestions);
- }
-
- public boolean showScrollingResults() {
- return mContext.getResources().getBoolean(R.bool.show_scrolling_results);
- }
-
- public Uri getHelpUrl(String activity) {
- return null;
- }
-
- public int getHttpConnectTimeout() {
- return HTTP_CONNECT_TIMEOUT_MILLIS;
- }
-
- public int getHttpReadTimeout() {
- return HTTP_READ_TIMEOUT_MILLIS;
- }
-
- public String getUserAgent() {
- return USER_AGENT;
- }
-}
diff --git a/src/com/android/quicksearchbox/Config.kt b/src/com/android/quicksearchbox/Config.kt
new file mode 100644
index 0000000..5a44058
--- /dev/null
+++ b/src/com/android/quicksearchbox/Config.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.AlarmManager
+import android.content.Context
+import android.net.Uri
+import android.os.Process
+import java.util.HashSet
+
+/**
+ * Provides values for configurable parameters in all of QSB.
+ *
+ * All the methods in this class return fixed default values. Subclasses may make these values
+ * server-side settable.
+ */
+class Config(context: Context?) {
+ private val mContext: Context?
+ private val mDefaultCorpora: HashSet<String>? = null
+ private val mHiddenCorpora: HashSet<String>? = null
+ private val mDefaultCorporaSuggestUris: HashSet<String>? = null
+ protected val context: Context?
+ get() = mContext
+
+ /**
+ * Releases any resources used by the configuration object.
+ *
+ * Default implementation does nothing.
+ */
+ fun close() {}
+ private fun loadResourceStringSet(res: Int): HashSet<String> {
+ val set: HashSet<String> = HashSet<String>()
+ val items: Array<String> = mContext?.getResources()!!.getStringArray(res)
+ for (item in items) {
+ set.add(item)
+ }
+ return set
+ }
+
+ /** The number of promoted sources. */
+ val numPromotedSources: Int
+ get() =
+ NUM_PROMOTED_SOURCES // Get the list of default corpora from a resource, which allows vendor
+ // overlays.
+
+ /** The number of suggestions visible above the onscreen keyboard. */
+ val numSuggestionsAboveKeyboard: Int
+ get() = // Get the list of default corpora from a resource, which allows vendor overlays.
+ mContext?.getResources()!!.getInteger(R.integer.num_suggestions_above_keyboard)
+
+ /** The maximum number of suggestions to promote. */
+ val maxPromotedSuggestions: Int
+ get() = mContext?.getResources()!!.getInteger(R.integer.max_promoted_suggestions)
+
+ val maxPromotedResults: Int
+ get() = mContext?.getResources()!!.getInteger(R.integer.max_promoted_results)
+
+ /** The number of results to ask each source for. */
+ val maxResultsPerSource: Int
+ get() = MAX_RESULTS_PER_SOURCE
+
+ /** The maximum number of shortcuts to show for the web source in All mode. */
+ val maxShortcutsPerWebSource: Int
+ get() = mContext?.getResources()!!.getInteger(R.integer.max_shortcuts_per_web_source)
+
+ /** The maximum number of shortcuts to show for each non-web source in All mode. */
+ val maxShortcutsPerNonWebSource: Int
+ get() = mContext?.getResources()!!.getInteger(R.integer.max_shortcuts_per_non_web_source)
+
+ /** Gets the maximum number of shortcuts that will be shown from the given source. */
+ @Suppress("UNUSED_PARAMETER")
+ fun getMaxShortcuts(sourceName: String?): Int {
+ return maxShortcutsPerNonWebSource
+ }
+
+ /** The timeout for querying each source, in milliseconds. */
+ val sourceTimeoutMillis: Long
+ get() = SOURCE_TIMEOUT_MILLIS
+
+ /**
+ * The priority of query threads.
+ *
+ * @return A thread priority, as defined in [Process].
+ */
+ val queryThreadPriority: Int
+ get() = QUERY_THREAD_PRIORITY
+
+ /** The maximum age of log data used for shortcuts. */
+ val maxStatAgeMillis: Long
+ get() = MAX_STAT_AGE_MILLIS
+
+ /** The minimum number of clicks needed to rank a source. */
+ val minClicksForSourceRanking: Int
+ get() = MIN_CLICKS_FOR_SOURCE_RANKING
+
+ val numWebCorpusThreads: Int
+ get() = NUM_WEB_CORPUS_THREADS
+
+ /**
+ * How often query latency should be logged.
+ *
+ * @return An integer in the range 0-1000. 0 means that no latency events should be logged. 1000
+ * means that all latency events should be logged.
+ */
+ val latencyLogFrequency: Int
+ get() = LATENCY_LOG_FREQUENCY
+
+ /**
+ * The delay in milliseconds before suggestions are updated while typing. If a new character is
+ * typed before this timeout expires, the timeout is reset.
+ */
+ val typingUpdateSuggestionsDelayMillis: Long
+ get() = TYPING_SUGGESTIONS_UPDATE_DELAY_MILLIS
+
+ fun allowVoiceSearchHints(): Boolean {
+ return true
+ }
+
+ /**
+ * The period of time for which after installing voice search we should consider showing voice
+ * search hints.
+ *
+ * @return The period in milliseconds.
+ */
+ val voiceSearchHintActivePeriod: Long
+ get() = VOICE_SEARCH_HINT_ACTIVE_PERIOD
+
+ /**
+ * The time interval at which we should consider whether or not to show some voice search hints.
+ *
+ * @return The period in milliseconds.
+ */
+ val voiceSearchHintUpdatePeriod: Long
+ get() = VOICE_SEARCH_HINT_UPDATE_INTERVAL
+
+ /**
+ * The time interval at which, on average, voice search hints are displayed.
+ *
+ * @return The period in milliseconds.
+ */
+ val voiceSearchHintShowPeriod: Long
+ get() = VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS
+
+ /**
+ * The amount of time for which voice search hints are displayed in one go.
+ *
+ * @return The period in milliseconds.
+ */
+ val voiceSearchHintVisibleTime: Long
+ get() = VOICE_SEARCH_HINT_VISIBLE_PERIOD
+
+ /**
+ * The period that we change voice search hints at while they're being displayed.
+ *
+ * @return The period in milliseconds.
+ */
+ val voiceSearchHintChangePeriod: Long
+ get() = VOICE_SEARCH_HINT_CHANGE_PERIOD
+
+ fun showSuggestionsForZeroQuery(): Boolean {
+ // Get the list of default corpora from a resource, which allows vendor overlays.
+ return mContext?.getResources()!!.getBoolean(R.bool.show_zero_query_suggestions)
+ }
+
+ fun showShortcutsForZeroQuery(): Boolean {
+ // Get the list of default corpora from a resource, which allows vendor overlays.
+ return mContext?.getResources()!!.getBoolean(R.bool.show_zero_query_shortcuts)
+ }
+
+ fun showScrollingSuggestions(): Boolean {
+ return mContext?.getResources()!!.getBoolean(R.bool.show_scrolling_suggestions)
+ }
+
+ fun showScrollingResults(): Boolean {
+ return mContext?.getResources()!!.getBoolean(R.bool.show_scrolling_results)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun getHelpUrl(activity: String?): Uri? {
+ return null
+ }
+
+ val httpConnectTimeout: Int
+ get() = HTTP_CONNECT_TIMEOUT_MILLIS
+
+ val httpReadTimeout: Int
+ get() = HTTP_READ_TIMEOUT_MILLIS
+
+ val userAgent: String
+ get() = USER_AGENT
+
+ companion object {
+ protected const val SECOND_MILLIS = 1000L
+
+ @JvmField protected val MINUTE_MILLIS: Long = 60L * SECOND_MILLIS
+ private val VOICE_SEARCH_HINT_CHANGE_PERIOD: Long = 2L * MINUTE_MILLIS
+ private val VOICE_SEARCH_HINT_VISIBLE_PERIOD: Long = 6L * MINUTE_MILLIS
+ protected const val DAY_MILLIS = 86400000L
+ private const val TAG = "QSB.Config"
+ private const val DBG = false
+ private const val NUM_PROMOTED_SOURCES = 3
+ private const val MAX_RESULTS_PER_SOURCE = 50
+ private const val SOURCE_TIMEOUT_MILLIS: Long = 10000
+ private val QUERY_THREAD_PRIORITY: Int =
+ Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE
+ private val MAX_STAT_AGE_MILLIS: Long = 30 * DAY_MILLIS
+ private const val MIN_CLICKS_FOR_SOURCE_RANKING = 3
+ private const val NUM_WEB_CORPUS_THREADS = 2
+ private const val LATENCY_LOG_FREQUENCY = 1000
+ private const val TYPING_SUGGESTIONS_UPDATE_DELAY_MILLIS: Long = 100
+ private const val PUBLISH_RESULT_DELAY_MILLIS: Long = 200
+ private val VOICE_SEARCH_HINT_ACTIVE_PERIOD: Long = 7L * DAY_MILLIS
+ private val VOICE_SEARCH_HINT_UPDATE_INTERVAL: Long = AlarmManager.INTERVAL_FIFTEEN_MINUTES
+ private val VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS: Long = AlarmManager.INTERVAL_HOUR * 2
+ private const val HTTP_CONNECT_TIMEOUT_MILLIS = 4000
+ private const val HTTP_READ_TIMEOUT_MILLIS = 4000
+ private const val USER_AGENT = "Android/1.0"
+ }
+
+ /** Creates a new config that uses hard-coded default values. */
+ init {
+ mContext = context
+ }
+}
diff --git a/src/com/android/quicksearchbox/CursorBackedSourceResult.java b/src/com/android/quicksearchbox/CursorBackedSourceResult.java
deleted file mode 100644
index 7c2fe9f..0000000
--- a/src/com/android/quicksearchbox/CursorBackedSourceResult.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.content.ComponentName;
-import android.database.Cursor;
-
-import com.android.quicksearchbox.google.GoogleSource;
-
-import java.util.Collection;
-
-public class CursorBackedSourceResult extends CursorBackedSuggestionCursor
- implements SourceResult {
-
- private final GoogleSource mSource;
-
- public CursorBackedSourceResult(GoogleSource source, String userQuery) {
- this(source, userQuery, null);
- }
-
- public CursorBackedSourceResult(GoogleSource source, String userQuery, Cursor cursor) {
- super(userQuery, cursor);
- mSource = source;
- }
-
- public GoogleSource getSource() {
- return mSource;
- }
-
- @Override
- public GoogleSource getSuggestionSource() {
- return mSource;
- }
-
- @Override
- public ComponentName getSuggestionIntentComponent() {
- return mSource.getIntentComponent();
- }
-
- public boolean isSuggestionShortcut() {
- return false;
- }
-
- public boolean isHistorySuggestion() {
- return false;
- }
-
- @Override
- public String toString() {
- return mSource + "[" + getUserQuery() + "]";
- }
-
- @Override
- public SuggestionExtras getExtras() {
- if (mCursor == null) return null;
- return CursorBackedSuggestionExtras.createExtrasIfNecessary(mCursor, getPosition());
- }
-
- public Collection<String> getExtraColumns() {
- if (mCursor == null) return null;
- return CursorBackedSuggestionExtras.getExtraColumns(mCursor);
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/quicksearchbox/CursorBackedSourceResult.kt b/src/com/android/quicksearchbox/CursorBackedSourceResult.kt
new file mode 100644
index 0000000..098fcb4
--- /dev/null
+++ b/src/com/android/quicksearchbox/CursorBackedSourceResult.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ComponentName
+import android.database.Cursor
+import com.android.quicksearchbox.google.GoogleSource
+import kotlin.collections.Collection
+
+class CursorBackedSourceResult(
+ override val suggestionSource: GoogleSource?,
+ userQuery: String?,
+ cursor: Cursor?
+) : CursorBackedSuggestionCursor(userQuery, cursor), SourceResult {
+
+ constructor(source: GoogleSource?, userQuery: String?) : this(source, userQuery, null)
+
+ override val source: Source?
+ get() = suggestionSource
+
+ @get:Override
+ override val suggestionIntentComponent: ComponentName?
+ get() = suggestionSource?.intentComponent
+
+ override val isSuggestionShortcut: Boolean
+ get() = false
+
+ override val isHistorySuggestion: Boolean
+ get() = false
+
+ @Override
+ override fun toString(): String {
+ return suggestionSource.toString() + "[" + userQuery + "]"
+ }
+
+ @get:Override
+ override val extras: SuggestionExtras?
+ get() =
+ if (mCursor == null) null
+ else CursorBackedSuggestionExtras.createExtrasIfNecessary(mCursor, position)!!
+
+ override val extraColumns: Collection<String>?
+ get() = if (mCursor == null) null else CursorBackedSuggestionExtras.getExtraColumns(mCursor)!!
+}
diff --git a/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java b/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java
deleted file mode 100644
index aa35164..0000000
--- a/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.SearchManager;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.net.Uri;
-import android.util.Log;
-
-public abstract class CursorBackedSuggestionCursor implements SuggestionCursor {
-
- private static final boolean DBG = false;
- protected static final String TAG = "QSB.CursorBackedSuggestionCursor";
-
- public static final String SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type";
-
- private final String mUserQuery;
-
- /** The suggestions, or {@code null} if the suggestions query failed. */
- protected final Cursor mCursor;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */
- private final int mFormatCol;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */
- private final int mText1Col;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */
- private final int mText2Col;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2_URL} in @{link mCursor}. */
- private final int mText2UrlCol;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
- private final int mIcon1Col;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
- private final int mIcon2Col;
-
- /** Column index of {@link SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING}
- * in @{link mCursor}.
- **/
- private final int mRefreshSpinnerCol;
-
- /** True if this result has been closed. */
- private boolean mClosed = false;
-
- public CursorBackedSuggestionCursor(String userQuery, Cursor cursor) {
- mUserQuery = userQuery;
- mCursor = cursor;
- mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
- mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
- mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
- mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
- mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
- mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING);
- }
-
- public String getUserQuery() {
- return mUserQuery;
- }
-
- public abstract Source getSuggestionSource();
-
- public String getSuggestionLogType() {
- return getStringOrNull(SUGGEST_COLUMN_LOG_TYPE);
- }
-
- public void close() {
- if (DBG) Log.d(TAG, "close()");
- if (mClosed) {
- throw new IllegalStateException("Double close()");
- }
- mClosed = true;
- if (mCursor != null) {
- try {
- mCursor.close();
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "close() failed, ", ex);
- }
- }
- }
-
- @Override
- protected void finalize() {
- if (!mClosed) {
- Log.e(TAG, "LEAK! Finalized without being closed: " + toString());
- }
- }
-
- public int getCount() {
- if (mClosed) {
- throw new IllegalStateException("getCount() after close()");
- }
- if (mCursor == null) return 0;
- try {
- return mCursor.getCount();
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getCount() failed, ", ex);
- return 0;
- }
- }
-
- public void moveTo(int pos) {
- if (mClosed) {
- throw new IllegalStateException("moveTo(" + pos + ") after close()");
- }
- try {
- if (!mCursor.moveToPosition(pos)) {
- Log.e(TAG, "moveToPosition(" + pos + ") failed, count=" + getCount());
- }
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "moveToPosition() failed, ", ex);
- }
- }
-
- public boolean moveToNext() {
- if (mClosed) {
- throw new IllegalStateException("moveToNext() after close()");
- }
- try {
- return mCursor.moveToNext();
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "moveToNext() failed, ", ex);
- return false;
- }
- }
-
- public int getPosition() {
- if (mClosed) {
- throw new IllegalStateException("getPosition after close()");
- }
- try {
- return mCursor.getPosition();
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getPosition() failed, ", ex);
- return -1;
- }
- }
-
- public String getShortcutId() {
- return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
- }
-
- public String getSuggestionFormat() {
- return getStringOrNull(mFormatCol);
- }
-
- public String getSuggestionText1() {
- return getStringOrNull(mText1Col);
- }
-
- public String getSuggestionText2() {
- return getStringOrNull(mText2Col);
- }
-
- public String getSuggestionText2Url() {
- return getStringOrNull(mText2UrlCol);
- }
-
- public String getSuggestionIcon1() {
- return getStringOrNull(mIcon1Col);
- }
-
- public String getSuggestionIcon2() {
- return getStringOrNull(mIcon2Col);
- }
-
- public boolean isSpinnerWhileRefreshing() {
- return "true".equals(getStringOrNull(mRefreshSpinnerCol));
- }
-
- /**
- * Gets the intent action for the current suggestion.
- */
- public String getSuggestionIntentAction() {
- String action = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
- if (action != null) return action;
- return getSuggestionSource().getDefaultIntentAction();
- }
-
- public abstract ComponentName getSuggestionIntentComponent();
-
- /**
- * Gets the query for the current suggestion.
- */
- public String getSuggestionQuery() {
- return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY);
- }
-
- public String getSuggestionIntentDataString() {
- // use specific data if supplied, or default data if supplied
- String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (data == null) {
- data = getSuggestionSource().getDefaultIntentData();
- }
- // then, if an ID was provided, append it.
- if (data != null) {
- String id = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
- if (id != null) {
- data = data + "/" + Uri.encode(id);
- }
- }
- return data;
- }
-
- /**
- * Gets the intent extra data for the current suggestion.
- */
- public String getSuggestionIntentExtraData() {
- return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
- }
-
- public boolean isWebSearchSuggestion() {
- return Intent.ACTION_WEB_SEARCH.equals(getSuggestionIntentAction());
- }
-
- /**
- * Gets the index of a column in {@link #mCursor} by name.
- *
- * @return The index, or {@code -1} if the column was not found.
- */
- protected int getColumnIndex(String colName) {
- if (mCursor == null) return -1;
- try {
- return mCursor.getColumnIndex(colName);
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getColumnIndex() failed, ", ex);
- return -1;
- }
- }
-
- /**
- * Gets the string value of a column in {@link #mCursor} by column index.
- *
- * @param col Column index.
- * @return The string value, or {@code null}.
- */
- protected String getStringOrNull(int col) {
- if (mCursor == null) return null;
- if (col == -1) {
- return null;
- }
- try {
- return mCursor.getString(col);
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getString() failed, ", ex);
- return null;
- }
- }
-
- /**
- * Gets the string value of a column in {@link #mCursor} by column name.
- *
- * @param colName Column name.
- * @return The string value, or {@code null}.
- */
- protected String getStringOrNull(String colName) {
- int col = getColumnIndex(colName);
- return getStringOrNull(col);
- }
-
- public void registerDataSetObserver(DataSetObserver observer) {
- // We don't watch Cursor-backed SuggestionCursors for changes
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- // We don't watch Cursor-backed SuggestionCursors for changes
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "[" + mUserQuery + "]";
- }
-
-}
diff --git a/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.kt b/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.kt
new file mode 100644
index 0000000..43776d6
--- /dev/null
+++ b/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.content.ComponentName
+import android.content.Intent
+import android.database.Cursor
+import android.database.DataSetObserver
+import android.net.Uri
+import android.util.Log
+
+abstract class CursorBackedSuggestionCursor(override val userQuery: String?, cursor: Cursor?) :
+ SuggestionCursor {
+
+ /** The suggestions, or `null` if the suggestions query failed. */
+ @JvmField protected val mCursor: Cursor?
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_FORMAT] in @{link mCursor}. */
+ private val mFormatCol: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_1] in @{link mCursor}. */
+ private val mText1Col: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2] in @{link mCursor}. */
+ private val mText2Col: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2_URL] in @{link mCursor}. */
+ private val mText2UrlCol: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */
+ private val mIcon1Col: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */
+ private val mIcon2Col: Int
+
+ /** Column index of [SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING] in @{link mCursor}. */
+ private val mRefreshSpinnerCol: Int
+
+ /** True if this result has been closed. */
+ private var mClosed = false
+ abstract override val suggestionSource: Source?
+ override val suggestionLogType: String?
+ get() = getStringOrNull(SUGGEST_COLUMN_LOG_TYPE)
+
+ override fun close() {
+ if (DBG) Log.d(TAG, "close()")
+ if (mClosed) {
+ throw IllegalStateException("Double close()")
+ }
+ mClosed = true
+ if (mCursor != null) {
+ try {
+ mCursor.close()
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "close() failed, ", ex)
+ }
+ }
+ }
+
+ @Override
+ protected fun finalize() {
+ if (!mClosed) {
+ Log.e(TAG, "LEAK! Finalized without being closed: " + toString())
+ }
+ }
+
+ override val count: Int
+ get() {
+ if (mClosed) {
+ throw IllegalStateException("getCount() after close()")
+ }
+ return if (mCursor == null) 0
+ else
+ try {
+ mCursor.getCount()
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "getCount() failed, ", ex)
+ 0
+ }
+ }
+
+ override fun moveTo(pos: Int) {
+ if (mClosed) {
+ throw IllegalStateException("moveTo($pos) after close()")
+ }
+ try {
+ if (!mCursor!!.moveToPosition(pos)) {
+ Log.e(TAG, "moveToPosition($pos) failed, count=$count")
+ }
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "moveToPosition() failed, ", ex)
+ }
+ }
+
+ override fun moveToNext(): Boolean {
+ if (mClosed) {
+ throw IllegalStateException("moveToNext() after close()")
+ }
+ return try {
+ mCursor!!.moveToNext()
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "moveToNext() failed, ", ex)
+ false
+ }
+ }
+
+ override val position: Int
+ get() {
+ if (mClosed) {
+ throw IllegalStateException("get() on position after close()")
+ }
+ return try {
+ mCursor!!.position
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "get() on position failed, ", ex)
+ -1
+ }
+ }
+ override val shortcutId: String?
+ get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID)
+ override val suggestionFormat: String?
+ get() = getStringOrNull(mFormatCol)
+ override val suggestionText1: String?
+ get() = getStringOrNull(mText1Col)
+ override val suggestionText2: String?
+ get() = getStringOrNull(mText2Col)
+ override val suggestionText2Url: String?
+ get() = getStringOrNull(mText2UrlCol)
+ override val suggestionIcon1: String?
+ get() = getStringOrNull(mIcon1Col)
+ override val suggestionIcon2: String?
+ get() = getStringOrNull(mIcon2Col)
+ override val isSpinnerWhileRefreshing: Boolean
+ get() = "true".equals(getStringOrNull(mRefreshSpinnerCol))
+
+ /** Gets the intent action for the current suggestion. */
+ override val suggestionIntentAction: String?
+ get() {
+ val action: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION)
+ return action
+ }
+ abstract override val suggestionIntentComponent: ComponentName?
+
+ /** Gets the query for the current suggestion. */
+ override val suggestionQuery: String?
+ get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY)
+
+ override val suggestionIntentDataString: String?
+ get() {
+ // use specific data if supplied, or default data if supplied
+ var data: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA)
+ if (data == null) {
+ data = suggestionSource?.defaultIntentData
+ }
+ // then, if an ID was provided, append it.
+ if (data != null) {
+ val id: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID)
+ if (id != null) {
+ data = data.toString() + "/" + Uri.encode(id)
+ }
+ }
+ return data
+ }
+
+ /** Gets the intent extra data for the current suggestion. */
+ override val suggestionIntentExtraData: String?
+ get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA)
+ override val isWebSearchSuggestion: Boolean
+ get() = Intent.ACTION_WEB_SEARCH.equals(suggestionIntentAction)
+
+ /**
+ * Gets the index of a column in [.mCursor] by name.
+ *
+ * @return The index, or `-1` if the column was not found.
+ */
+ protected fun getColumnIndex(colName: String?): Int {
+ return if (mCursor == null) -1
+ else
+ try {
+ mCursor.getColumnIndex(colName)
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "getColumnIndex() failed, ", ex)
+ -1
+ }
+ }
+
+ /**
+ * Gets the string value of a column in [.mCursor] by column index.
+ *
+ * @param col Column index.
+ * @return The string value, or `null`.
+ */
+ protected fun getStringOrNull(col: Int): String? {
+ if (mCursor == null) return null
+ return if (col == -1) {
+ null
+ } else
+ try {
+ mCursor.getString(col)
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(TAG, "getString() failed, ", ex)
+ null
+ }
+ }
+
+ /**
+ * Gets the string value of a column in [.mCursor] by column name.
+ *
+ * @param colName Column name.
+ * @return The string value, or `null`.
+ */
+ protected fun getStringOrNull(colName: String?): String? {
+ val col = getColumnIndex(colName)
+ return getStringOrNull(col)
+ }
+
+ override fun registerDataSetObserver(observer: DataSetObserver?) {
+ // We don't watch Cursor-backed SuggestionCursors for changes
+ }
+
+ override fun unregisterDataSetObserver(observer: DataSetObserver?) {
+ // We don't watch Cursor-backed SuggestionCursors for changes
+ }
+
+ @Override
+ override fun toString(): String {
+ return this::class.simpleName.toString() + "[" + userQuery + "]"
+ }
+
+ companion object {
+ private const val DBG = false
+ protected const val TAG = "QSB.CursorBackedSuggestionCursor"
+ const val SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type"
+ }
+
+ init {
+ mCursor = cursor
+ mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT)
+ mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
+ mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2)
+ mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL)
+ mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1)
+ mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2)
+ mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING)
+ }
+}
diff --git a/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.java b/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.java
deleted file mode 100644
index b6d85ff..0000000
--- a/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.database.Cursor;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * SuggestionExtras taking values from the extra columns in a suggestion cursor.
- */
-public class CursorBackedSuggestionExtras extends AbstractSuggestionExtras {
- private static final String TAG = "QSB.CursorBackedSuggestionExtras";
-
- private static final HashSet<String> DEFAULT_COLUMNS = new HashSet<String>();
- static {
- DEFAULT_COLUMNS.addAll(Arrays.asList(SuggestionCursorBackedCursor.COLUMNS));
- }
-
- private final Cursor mCursor;
- private final int mCursorPosition;
- private final List<String> mExtraColumns;
-
- static CursorBackedSuggestionExtras createExtrasIfNecessary(Cursor cursor, int position) {
- List<String> extraColumns = getExtraColumns(cursor);
- if (extraColumns != null) {
- return new CursorBackedSuggestionExtras(cursor, position, extraColumns);
- } else {
- return null;
- }
- }
-
- static String[] getCursorColumns(Cursor cursor) {
- try {
- return cursor.getColumnNames();
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getColumnNames() failed, ", ex);
- return null;
- }
- }
-
- static boolean cursorContainsExtras(Cursor cursor) {
- String[] columns = getCursorColumns(cursor);
- for (String cursorColumn : columns) {
- if (!DEFAULT_COLUMNS.contains(cursorColumn)) {
- return true;
- }
- }
- return false;
- }
-
- static List<String> getExtraColumns(Cursor cursor) {
- String[] columns = getCursorColumns(cursor);
- if (columns == null) return null;
- List<String> extraColumns = null;
- for (String cursorColumn : columns) {
- if (!DEFAULT_COLUMNS.contains(cursorColumn)) {
- if (extraColumns == null) {
- extraColumns = new ArrayList<String>();
- }
- extraColumns.add(cursorColumn);
- }
- }
- return extraColumns;
- }
-
- private CursorBackedSuggestionExtras(Cursor cursor, int position, List<String> extraColumns) {
- super(null);
- mCursor = cursor;
- mCursorPosition = position;
- mExtraColumns = extraColumns;
- }
-
- @Override
- public String doGetExtra(String columnName) {
- try {
- mCursor.moveToPosition(mCursorPosition);
- int columnIdx = mCursor.getColumnIndex(columnName);
- if (columnIdx < 0) return null;
- return mCursor.getString(columnIdx);
- } catch (RuntimeException ex) {
- // all operations on cross-process cursors can throw random exceptions
- Log.e(TAG, "getExtra(" + columnName + ") failed, ", ex);
- return null;
- }
- }
-
- @Override
- public List<String> doGetExtraColumnNames() {
- return mExtraColumns;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.kt b/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.kt
new file mode 100644
index 0000000..2766488
--- /dev/null
+++ b/src/com/android/quicksearchbox/CursorBackedSuggestionExtras.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.database.Cursor
+import android.util.Log
+import kotlin.Array
+import kotlin.collections.ArrayList
+import kotlin.collections.HashSet
+
+/** SuggestionExtras taking values from the extra columns in a suggestion cursor. */
+class CursorBackedSuggestionExtras
+private constructor(cursor: Cursor?, position: Int, extraColumns: List<String>) :
+ AbstractSuggestionExtras(null) {
+ companion object {
+ private const val TAG = "QSB.CursorBackedSuggestionExtras"
+ private val DEFAULT_COLUMNS: HashSet<String> = HashSet<String>()
+ @JvmStatic
+ fun createExtrasIfNecessary(cursor: Cursor?, position: Int): CursorBackedSuggestionExtras? {
+ val extraColumns: List<String>? =
+ CursorBackedSuggestionExtras.Companion.getExtraColumns(cursor)
+ return if (extraColumns != null) {
+ CursorBackedSuggestionExtras(cursor, position, extraColumns)
+ } else {
+ null
+ }
+ }
+
+ @JvmStatic
+ fun getCursorColumns(cursor: Cursor?): Array<String>? {
+ return try {
+ cursor?.getColumnNames()
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(CursorBackedSuggestionExtras.Companion.TAG, "getColumnNames() failed, ", ex)
+ null
+ }
+ }
+
+ fun cursorContainsExtras(cursor: Cursor?): Boolean {
+ val columns: Array<String>? = CursorBackedSuggestionExtras.Companion.getCursorColumns(cursor)
+ if (columns != null) {
+ for (cursorColumn in columns) {
+ if (!CursorBackedSuggestionExtras.Companion.DEFAULT_COLUMNS.contains(cursorColumn)) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ @JvmStatic
+ fun getExtraColumns(cursor: Cursor?): List<String>? {
+ val columns: Array<String> =
+ CursorBackedSuggestionExtras.Companion.getCursorColumns(cursor) ?: return null
+ var extraColumns: ArrayList<String>? = null
+ for (cursorColumn in columns) {
+ if (!CursorBackedSuggestionExtras.Companion.DEFAULT_COLUMNS.contains(cursorColumn)) {
+ if (extraColumns == null) {
+ extraColumns = arrayListOf<String>()
+ }
+ extraColumns.add(cursorColumn)
+ }
+ }
+ return extraColumns
+ }
+
+ init {
+ CursorBackedSuggestionExtras.Companion.DEFAULT_COLUMNS.addAll(
+ SuggestionCursorBackedCursor.COLUMNS.asList()
+ )
+ }
+ }
+
+ private val mCursor: Cursor?
+ private val mCursorPosition: Int
+ private val mExtraColumns: List<String>
+ @Override
+ override fun doGetExtra(columnName: String?): String? {
+ return try {
+ mCursor?.moveToPosition(mCursorPosition)
+ val columnIdx: Int = mCursor!!.getColumnIndex(columnName)
+ if (columnIdx < 0) null else mCursor.getString(columnIdx)
+ } catch (ex: RuntimeException) {
+ // all operations on cross-process cursors can throw random exceptions
+ Log.e(CursorBackedSuggestionExtras.Companion.TAG, "getExtra($columnName) failed, ", ex)
+ null
+ }
+ }
+
+ @Override
+ public override fun doGetExtraColumnNames(): List<String> {
+ return mExtraColumns
+ }
+
+ init {
+ mCursor = cursor
+ mCursorPosition = position
+ mExtraColumns = extraColumns
+ }
+}
diff --git a/src/com/android/quicksearchbox/DialogActivity.java b/src/com/android/quicksearchbox/DialogActivity.java
deleted file mode 100644
index 276b688..0000000
--- a/src/com/android/quicksearchbox/DialogActivity.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-import android.view.Window;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-/**
- * Activity that looks like a dialog window.
- */
-public abstract class DialogActivity extends Activity {
-
- protected TextView mTitleView;
- protected FrameLayout mContentFrame;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().requestFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.dialog_activity);
- mTitleView = (TextView) findViewById(R.id.alertTitle);
- mContentFrame = (FrameLayout) findViewById(R.id.content);
- }
-
- public void setHeading(int titleRes) {
- mTitleView.setText(titleRes);
- }
-
- public void setHeading(CharSequence title) {
- mTitleView.setText(title);
- }
-
- public void setDialogContent(int layoutRes) {
- mContentFrame.removeAllViews();
- getLayoutInflater().inflate(layoutRes, mContentFrame);
- }
-
- public void setDialogContent(View content) {
- mContentFrame.removeAllViews();
- mContentFrame.addView(content);
- }
-
- public View getDialogContent() {
- if (mContentFrame.getChildCount() > 0) {
- return mContentFrame.getChildAt(0);
- } else {
- return null;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/DialogActivity.kt b/src/com/android/quicksearchbox/DialogActivity.kt
new file mode 100644
index 0000000..ffbb376
--- /dev/null
+++ b/src/com/android/quicksearchbox/DialogActivity.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.view.Window
+import android.widget.FrameLayout
+import android.widget.TextView
+
+/** Activity that looks like a dialog window. */
+abstract class DialogActivity : Activity() {
+ @JvmField protected var mTitleView: TextView? = null
+
+ @JvmField protected var mContentFrame: FrameLayout? = null
+
+ @Override
+ protected override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ getWindow().requestFeature(Window.FEATURE_NO_TITLE)
+ setContentView(R.layout.dialog_activity)
+ mTitleView = findViewById(R.id.alertTitle) as TextView?
+ mContentFrame = findViewById(R.id.content) as FrameLayout?
+ }
+
+ fun setHeading(titleRes: Int) {
+ mTitleView?.setText(titleRes)
+ }
+
+ fun setHeading(title: CharSequence?) {
+ mTitleView?.setText(title)
+ }
+
+ fun setDialogContent(layoutRes: Int) {
+ mContentFrame?.removeAllViews()
+ getLayoutInflater().inflate(layoutRes, mContentFrame)
+ }
+
+ fun setDialogContent(content: View?) {
+ mContentFrame?.removeAllViews()
+ mContentFrame?.addView(content)
+ }
+}
diff --git a/src/com/android/quicksearchbox/EventLogLogger.java b/src/com/android/quicksearchbox/EventLogLogger.java
deleted file mode 100644
index 17ec0d1..0000000
--- a/src/com/android/quicksearchbox/EventLogLogger.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.content.Context;
-import android.util.EventLog;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Logs events to {@link EventLog}.
- */
-public class EventLogLogger implements Logger {
-
- private static final char LIST_SEPARATOR = '|';
-
- private final Context mContext;
-
- private final Config mConfig;
-
- private final String mPackageName;
-
- private final Random mRandom;
-
- public EventLogLogger(Context context, Config config) {
- mContext = context;
- mConfig = config;
- mPackageName = mContext.getPackageName();
- mRandom = new Random();
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected int getVersionCode() {
- return QsbApplication.get(getContext()).getVersionCode();
- }
-
- protected Config getConfig() {
- return mConfig;
- }
-
- @Override
- public void logStart(int onCreateLatency, int latency, String intentSource) {
- // TODO: Add more info to startMethod
- String startMethod = intentSource;
- EventLogTags.writeQsbStart(mPackageName, getVersionCode(), startMethod,
- latency, null, null, onCreateLatency);
- }
-
- @Override
- public void logSuggestionClick(long id, SuggestionCursor suggestionCursor, int clickType) {
- String suggestions = getSuggestions(suggestionCursor);
- int numChars = suggestionCursor.getUserQuery().length();
- EventLogTags.writeQsbClick(id, suggestions, null, numChars,
- clickType);
- }
-
- @Override
- public void logSearch(int startMethod, int numChars) {
- EventLogTags.writeQsbSearch(null, startMethod, numChars);
- }
-
- @Override
- public void logVoiceSearch() {
- EventLogTags.writeQsbVoiceSearch(null);
- }
-
- @Override
- public void logExit(SuggestionCursor suggestionCursor, int numChars) {
- String suggestions = getSuggestions(suggestionCursor);
- EventLogTags.writeQsbExit(suggestions, numChars);
- }
-
- @Override
- public void logLatency(SourceResult result) {
- }
-
- private String getSuggestions(SuggestionCursor cursor) {
- StringBuilder sb = new StringBuilder();
- final int count = cursor == null ? 0 : cursor.getCount();
- for (int i = 0; i < count; i++) {
- if (i > 0) sb.append(LIST_SEPARATOR);
- cursor.moveTo(i);
- String source = cursor.getSuggestionSource().getName();
- String type = cursor.getSuggestionLogType();
- if (type == null) type = "";
- String shortcut = cursor.isSuggestionShortcut() ? "shortcut" : "";
- sb.append(source).append(':').append(type).append(':').append(shortcut);
- }
- return sb.toString();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/EventLogLogger.kt b/src/com/android/quicksearchbox/EventLogLogger.kt
new file mode 100644
index 0000000..a367b4e
--- /dev/null
+++ b/src/com/android/quicksearchbox/EventLogLogger.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.Context
+import android.util.EventLog
+import java.util.Random
+import kotlin.text.StringBuilder
+
+/** Logs events to [EventLog]. */
+class EventLogLogger(context: Context?, config: Config) : Logger {
+ private val mContext: Context?
+ protected val config: Config
+ private val mPackageName: String
+ private val mRandom: Random
+ protected val context: Context?
+ get() = mContext
+ protected val versionCode: Long
+ get() = QsbApplication.get(context).versionCode
+
+ @Override
+ override fun logStart(onCreateLatency: Int, latency: Int, intentSource: String?) {
+ // TODO: Add more info to startMethod
+ EventLogTags.writeQsbStart(
+ mPackageName,
+ versionCode.toInt(),
+ intentSource,
+ latency,
+ null,
+ null,
+ onCreateLatency
+ )
+ }
+
+ @Override
+ override fun logSuggestionClick(
+ suggestionId: Long,
+ suggestionCursor: SuggestionCursor?,
+ clickType: Int
+ ) {
+ val suggestions = getSuggestions(suggestionCursor)
+ val numChars: Int = suggestionCursor!!.userQuery!!.length
+ EventLogTags.writeQsbClick(suggestionId, suggestions, null, numChars, clickType)
+ }
+
+ @Override
+ override fun logSearch(startMethod: Int, numChars: Int) {
+ EventLogTags.writeQsbSearch(null, startMethod, numChars)
+ }
+
+ @Override
+ override fun logVoiceSearch() {
+ EventLogTags.writeQsbVoiceSearch(null)
+ }
+
+ @Override
+ override fun logExit(suggestionCursor: SuggestionCursor?, numChars: Int) {
+ val suggestions = getSuggestions(suggestionCursor)
+ EventLogTags.writeQsbExit(suggestions, numChars)
+ }
+
+ @Override override fun logLatency(result: SourceResult?) {}
+
+ private fun getSuggestions(cursor: SuggestionCursor?): String {
+ val sb = StringBuilder()
+ val count = cursor?.count ?: 0
+ for (i in 0 until count) {
+ if (i > 0) sb.append(LIST_SEPARATOR)
+ cursor!!.moveTo(i)
+ val source: String? = cursor.suggestionSource?.name
+ var type: String? = cursor.suggestionLogType
+ if (type == null) type = ""
+ val shortcut = if (cursor.isSuggestionShortcut) "shortcut" else ""
+ sb.append(source).append(":").append(type).append(":").append(shortcut)
+ }
+ return sb.toString()
+ }
+
+ companion object {
+ private const val LIST_SEPARATOR = '|'
+ }
+
+ init {
+ mContext = context
+ this.config = config
+ mPackageName = mContext!!.getPackageName()
+ mRandom = Random()
+ }
+}
diff --git a/src/com/android/quicksearchbox/Help.java b/src/com/android/quicksearchbox/Help.java
deleted file mode 100644
index 9c86811..0000000
--- a/src/com/android/quicksearchbox/Help.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-/**
- * Handles app help.
- */
-public class Help {
-
- private final Context mContext;
- private final Config mConfig;
-
- public Help(Context context, Config config) {
- mContext = context;
- mConfig = config;
- }
-
- public void addHelpMenuItem(Menu menu, String activityName) {
- addHelpMenuItem(menu, activityName, false);
- }
-
- public void addHelpMenuItem(Menu menu, String activityName, boolean showAsAction) {
- Intent helpIntent = getHelpIntent(activityName);
- if (helpIntent != null) {
- MenuInflater inflater = new MenuInflater(mContext);
- inflater.inflate(R.menu.help, menu);
- MenuItem item = menu.findItem(R.id.menu_help);
- item.setIntent(helpIntent);
- if (showAsAction) {
- item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- }
- }
- }
-
- private Intent getHelpIntent(String activityName) {
- Uri helpUrl = mConfig.getHelpUrl(activityName);
- if (helpUrl == null) return null;
- return new Intent(Intent.ACTION_VIEW, helpUrl);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/Help.kt b/src/com/android/quicksearchbox/Help.kt
new file mode 100644
index 0000000..ec0a773
--- /dev/null
+++ b/src/com/android/quicksearchbox/Help.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+
+/** Handles app help. */
+class Help(context: Context?, config: Config) {
+ private val mContext: Context?
+ private val mConfig: Config
+ fun addHelpMenuItem(menu: Menu, activityName: String?) {
+ addHelpMenuItem(menu, activityName, false)
+ }
+
+ fun addHelpMenuItem(menu: Menu, activityName: String?, showAsAction: Boolean) {
+ val helpIntent: Intent? = getHelpIntent(activityName)
+ if (helpIntent != null) {
+ val inflater = MenuInflater(mContext)
+ inflater.inflate(R.menu.help, menu)
+ val item: MenuItem = menu.findItem(R.id.menu_help)
+ item.setIntent(helpIntent)
+ if (showAsAction) {
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
+ }
+ }
+ }
+
+ private fun getHelpIntent(activityName: String?): Intent? {
+ val helpUrl: Uri = mConfig.getHelpUrl(activityName) ?: return null
+ return Intent(Intent.ACTION_VIEW, helpUrl)
+ }
+
+ init {
+ mContext = context
+ mConfig = config
+ }
+}
diff --git a/src/com/android/quicksearchbox/IconLoader.java b/src/com/android/quicksearchbox/IconLoader.java
deleted file mode 100644
index 191ca33..0000000
--- a/src/com/android/quicksearchbox/IconLoader.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.NowOrLater;
-
-import android.content.ContentResolver;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-
-/**
- * Interface for icon loaders.
- *
- */
-public interface IconLoader {
-
- /**
- * Gets a drawable given an ID.
- *
- * The ID could be just the string value of a resource id
- * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
- * the provider's resources. If the ID is not an integer, it is
- * treated as a Uri and opened with
- * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
- *
- * All resources and URIs are read using the suggestion provider's context.
- *
- * @return a {@link NowOrLater} for retrieving the icon. If the ID is not formatted as expected,
- * or no drawable can be found for the provided value, the value from this will be null.
- *
- * @param drawableId a string like "2130837524",
- * "android.resource://com.android.alarmclock/2130837524",
- * or "content://contacts/photos/253".
- */
- NowOrLater<Drawable> getIcon(String drawableId);
-
- /**
- * Converts a drawable ID to a Uri that can be used from other packages.
- */
- Uri getIconUri(String drawableId);
-
-}
diff --git a/src/com/android/quicksearchbox/IconLoader.kt b/src/com/android/quicksearchbox/IconLoader.kt
new file mode 100644
index 0000000..0752c70
--- /dev/null
+++ b/src/com/android/quicksearchbox/IconLoader.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ContentResolver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import com.android.quicksearchbox.util.NowOrLater
+
+/** Interface for icon loaders. */
+interface IconLoader {
+ /**
+ * Gets a drawable given an ID.
+ *
+ * The ID could be just the string value of a resource id (e.g., "2130837524"), in which case we
+ * will try to retrieve a drawable from the provider's resources. If the ID is not an integer, it
+ * is treated as a Uri and opened with [ContentResolver.openOutputStream].
+ *
+ * All resources and URIs are read using the suggestion provider's context.
+ *
+ * @return a [NowOrLater] for retrieving the icon. If the ID is not formatted as expected, or no
+ * drawable can be found for the provided value, the value from this will be null.
+ *
+ * @param drawableId a string like "2130837524",
+ * "android.resource://com.android.alarmclock/2130837524", or "content://contacts/photos/253".
+ */
+ fun getIcon(drawableId: String?): NowOrLater<Drawable?>?
+
+ /** Converts a drawable ID to a Uri that can be used from other packages. */
+ fun getIconUri(drawableId: String?): Uri?
+}
diff --git a/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.java b/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.java
deleted file mode 100644
index 418a0b0..0000000
--- a/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-
-/**
- * SuggestionExtras taking values from a {@link JSONObject}.
- */
-public class JsonBackedSuggestionExtras implements SuggestionExtras {
- private static final String TAG = "QSB.JsonBackedSuggestionExtras";
-
- private final JSONObject mExtras;
- private final Collection<String> mColumns;
-
- public JsonBackedSuggestionExtras(String json) throws JSONException {
- mExtras = new JSONObject(json);
- mColumns = new ArrayList<String>(mExtras.length());
- Iterator<String> it = mExtras.keys();
- while (it.hasNext()) {
- mColumns.add(it.next());
- }
- }
-
- public JsonBackedSuggestionExtras(SuggestionExtras extras) throws JSONException {
- mExtras = new JSONObject();
- mColumns = extras.getExtraColumnNames();
- for (String column : extras.getExtraColumnNames()) {
- String value = extras.getExtra(column);
- mExtras.put(column, value == null ? JSONObject.NULL : value);
- }
- }
-
- public String getExtra(String columnName) {
- try {
- if (mExtras.isNull(columnName)) {
- return null;
- } else {
- return mExtras.getString(columnName);
- }
- } catch (JSONException e) {
- Log.w(TAG, "Could not extract JSON extra", e);
- return null;
- }
- }
-
- public Collection<String> getExtraColumnNames() {
- return mColumns;
- }
-
- @Override
- public String toString() {
- return mExtras.toString();
- }
-
- public String toJsonString() {
- return toString();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.kt b/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.kt
new file mode 100644
index 0000000..0baed07
--- /dev/null
+++ b/src/com/android/quicksearchbox/JsonBackedSuggestionExtras.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.util.Log
+import java.util.ArrayList
+import org.json.JSONException
+import org.json.JSONObject
+
+/** SuggestionExtras taking values from a [JSONObject]. */
+class JsonBackedSuggestionExtras : SuggestionExtras {
+ private val mExtras: JSONObject
+ override val extraColumnNames: Collection<String>
+
+ constructor(json: String?) {
+ mExtras = JSONObject(json!!)
+ extraColumnNames = ArrayList<String>(mExtras.length())
+ val it: Iterator<String> = mExtras.keys()
+ while (it.hasNext()) {
+ extraColumnNames.add(it.next())
+ }
+ }
+
+ constructor(extras: SuggestionExtras) {
+ mExtras = JSONObject()
+ extraColumnNames = extras.extraColumnNames
+ for (column in extras.extraColumnNames) {
+ val value = extras.getExtra(column)
+ mExtras.put(column, value ?: JSONObject.NULL)
+ }
+ }
+
+ override fun getExtra(columnName: String?): String? {
+ return try {
+ if (mExtras.isNull(columnName)) {
+ null
+ } else {
+ mExtras.getString(columnName!!)
+ }
+ } catch (e: JSONException) {
+ Log.w(TAG, "Could not extract JSON extra", e)
+ null
+ }
+ }
+
+ @Override
+ override fun toString(): String {
+ return mExtras.toString()
+ }
+
+ override fun toJsonString(): String? {
+ return toString()
+ }
+
+ companion object {
+ private const val TAG = "QSB.JsonBackedSuggestionExtras"
+ }
+}
diff --git a/src/com/android/quicksearchbox/LatencyTracker.java b/src/com/android/quicksearchbox/LatencyTracker.java
deleted file mode 100644
index 6c9472c..0000000
--- a/src/com/android/quicksearchbox/LatencyTracker.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.os.SystemClock;
-
-/**
- * Tracks latency in wall-clock time. Since {@link #getLatency} returns an {@code int},
- * latencies over 2^31 ms (~ 25 days) cannot be measured.
- * This class uses {@link SystemClock#uptimeMillis} which does not advance during deep sleep.
- */
-public class LatencyTracker {
-
- /**
- * Start time, in milliseconds as returned by {@link SystemClock#uptimeMillis}.
- */
- private long mStartTime;
-
- /**
- * Creates a new latency tracker and sets the start time.
- */
- public LatencyTracker() {
- mStartTime = SystemClock.uptimeMillis();
- }
-
- /**
- * Resets the start time.
- */
- public void reset() {
- mStartTime = SystemClock.uptimeMillis();
- }
-
- /**
- * Gets the number of milliseconds since the object was created, or {@link #reset} was called.
- */
- public int getLatency() {
- long now = SystemClock.uptimeMillis();
- return (int) (now - mStartTime);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/LatencyTracker.kt b/src/com/android/quicksearchbox/LatencyTracker.kt
new file mode 100644
index 0000000..53e7467
--- /dev/null
+++ b/src/com/android/quicksearchbox/LatencyTracker.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.os.SystemClock
+
+/**
+ * Tracks latency in wall-clock time. Since [.getLatency] returns an `int`, latencies over 2^31 ms
+ * (~ 25 days) cannot be measured. This class uses [SystemClock.uptimeMillis] which does not advance
+ * during deep sleep.
+ */
+class LatencyTracker {
+ /** Start time, in milliseconds as returned by [SystemClock.uptimeMillis]. */
+ private var mStartTime: Long
+
+ /** Resets the start time. */
+ fun reset() {
+ mStartTime = SystemClock.uptimeMillis()
+ }
+
+ /** Gets the number of milliseconds since the object was created, or [.reset] was called. */
+ val latency: Int
+ get() {
+ val now: Long = SystemClock.uptimeMillis()
+ return (now - mStartTime).toInt()
+ }
+
+ /** Creates a new latency tracker and sets the start time. */
+ init {
+ mStartTime = SystemClock.uptimeMillis()
+ }
+}
diff --git a/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.java b/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.java
deleted file mode 100644
index dc12db1..0000000
--- a/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.LevenshteinDistance;
-import com.android.quicksearchbox.util.LevenshteinDistance.Token;
-import com.google.common.annotations.VisibleForTesting;
-
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.util.Log;
-
-/**
- * Suggestion formatter using the Levenshtein distance (minumum edit distance) to calculate the
- * formatting.
- */
-public class LevenshteinSuggestionFormatter extends SuggestionFormatter {
- private static final boolean DBG = false;
- private static final String TAG = "QSB.LevenshteinSuggestionFormatter";
-
- public LevenshteinSuggestionFormatter(TextAppearanceFactory spanFactory) {
- super(spanFactory);
- }
-
- @Override
- public Spanned formatSuggestion(String query, String suggestion) {
- if (DBG) Log.d(TAG, "formatSuggestion('" + query + "', '" + suggestion + "')");
- query = normalizeQuery(query);
- final Token[] queryTokens = tokenize(query);
- final Token[] suggestionTokens = tokenize(suggestion);
- final int[] matches = findMatches(queryTokens, suggestionTokens);
- if (DBG){
- Log.d(TAG, "source = " + queryTokens);
- Log.d(TAG, "target = " + suggestionTokens);
- Log.d(TAG, "matches = " + matches);
- }
- final SpannableString str = new SpannableString(suggestion);
-
- final int matchesLen = matches.length;
- for (int i = 0; i < matchesLen; ++i) {
- final Token t = suggestionTokens[i];
- int sourceLen = 0;
- int thisMatch = matches[i];
- if (thisMatch >= 0) {
- sourceLen = queryTokens[thisMatch].length();
- }
- applySuggestedTextStyle(str, t.mStart + sourceLen, t.mEnd);
- applyQueryTextStyle(str, t.mStart, t.mStart + sourceLen);
- }
-
- return str;
- }
-
- private String normalizeQuery(String query) {
- return query.toLowerCase();
- }
-
- /**
- * Finds which tokens in the target match tokens in the source.
- *
- * @param source List of source tokens (i.e. user query)
- * @param target List of target tokens (i.e. suggestion)
- * @return The indices into source which target tokens correspond to. A non-negative value n at
- * position i means that target token i matches source token n. A negative value means that
- * the target token i does not match any source token.
- */
- @VisibleForTesting
- int[] findMatches(Token[] source, Token[] target) {
- final LevenshteinDistance table = new LevenshteinDistance(source, target);
- table.calculate();
- final int targetLen = target.length;
- final int[] result = new int[targetLen];
- LevenshteinDistance.EditOperation[] ops = table.getTargetOperations();
- for (int i = 0; i < targetLen; ++i) {
- if (ops[i].getType() == LevenshteinDistance.EDIT_UNCHANGED) {
- result[i] = ops[i].getPosition();
- } else {
- result[i] = -1;
- }
- }
- return result;
- }
-
- @VisibleForTesting
- Token[] tokenize(final String seq) {
- int pos = 0;
- final int len = seq.length();
- final char[] chars = seq.toCharArray();
- // There can't be more tokens than characters, make an array that is large enough
- Token[] tokens = new Token[len];
- int tokenCount = 0;
- while (pos < len) {
- while (pos < len && (chars[pos] == ' ' || chars[pos] == '\t')) {
- pos++;
- }
- int start = pos;
- while (pos < len && !(chars[pos] == ' ' || chars[pos] == '\t')) {
- pos++;
- }
- int end = pos;
- if (start != end) {
- tokens[tokenCount++] = new Token(chars, start, end);
- }
- }
- // Create a token array of the right size and return
- Token[] ret = new Token[tokenCount];
- System.arraycopy(tokens, 0, ret, 0, tokenCount);
- return ret;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.kt b/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.kt
new file mode 100644
index 0000000..de1cd22
--- /dev/null
+++ b/src/com/android/quicksearchbox/LevenshteinSuggestionFormatter.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.text.SpannableString
+import android.text.Spanned
+import android.util.Log
+import com.android.quicksearchbox.util.LevenshteinDistance
+import com.android.quicksearchbox.util.LevenshteinDistance.Token
+import com.google.common.annotations.VisibleForTesting
+import java.util.Locale
+
+/**
+ * Suggestion formatter using the Levenshtein distance (minimum edit distance) to calculate the
+ * formatting.
+ */
+class LevenshteinSuggestionFormatter(spanFactory: TextAppearanceFactory?) :
+ SuggestionFormatter(spanFactory!!) {
+ @Override
+ override fun formatSuggestion(query: String?, suggestion: String?): Spanned {
+ var mQuery = query
+ if (DBG) Log.d(TAG, "formatSuggestion('$mQuery', '$suggestion')")
+ mQuery = normalizeQuery(mQuery)
+ val queryTokens: Array<Token?> = tokenize(mQuery)
+ val suggestionTokens: Array<Token?> = tokenize(suggestion)
+ val matches = findMatches(queryTokens, suggestionTokens)
+ if (DBG) {
+ Log.d(TAG, "source = $queryTokens")
+ Log.d(TAG, "target = $suggestionTokens")
+ Log.d(TAG, "matches = $matches")
+ }
+ val str = SpannableString(suggestion)
+ val matchesLen = matches.size
+ for (i in 0 until matchesLen) {
+ val t: Token? = suggestionTokens[i]
+ var sourceLen = 0
+ val thisMatch = matches[i]
+ if (thisMatch >= 0) {
+ sourceLen = queryTokens[thisMatch]!!.length
+ }
+ applySuggestedTextStyle(str, t!!.mStart + sourceLen, t.mEnd)
+ applyQueryTextStyle(str, t.mStart, t.mStart + sourceLen)
+ }
+ return str
+ }
+
+ private fun normalizeQuery(query: String?): String? {
+ return query?.lowercase(Locale.getDefault())
+ }
+
+ /**
+ * Finds which tokens in the target match tokens in the source.
+ *
+ * @param source List of source tokens (i.e. user query)
+ * @param target List of target tokens (i.e. suggestion)
+ * @return The indices into source which target tokens correspond to. A non-negative value n at
+ * position i means that target token i matches source token n. A negative value means that the
+ * target token i does not match any source token.
+ */
+ @VisibleForTesting
+ fun findMatches(source: Array<Token?>?, target: Array<Token?>): IntArray {
+ val table = LevenshteinDistance(source, target)
+ table.calculate()
+ val targetLen = target.size
+ val result = IntArray(targetLen)
+ val ops: Array<LevenshteinDistance.EditOperation?> = table.targetOperations
+ for (i in 0 until targetLen) {
+ if (ops[i]!!.type == LevenshteinDistance.EDIT_UNCHANGED) {
+ result[i] = ops[i]!!.position
+ } else {
+ result[i] = -1
+ }
+ }
+ return result
+ }
+
+ @VisibleForTesting
+ fun tokenize(seq: String?): Array<Token?> {
+ var pos = 0
+ val len: Int = seq!!.length
+ val chars = seq.toCharArray()
+ // There can't be more tokens than characters, make an array that is large enough
+ val tokens: Array<Token?> = arrayOfNulls<Token>(len)
+ var tokenCount = 0
+ while (pos < len) {
+ while (pos < len && (chars[pos] == ' ' || chars[pos] == '\t')) {
+ pos++
+ }
+ val start = pos
+ while (pos < len && !(chars[pos] == ' ' || chars[pos] == '\t')) {
+ pos++
+ }
+ val end = pos
+ if (start != end) {
+ tokens[tokenCount++] = Token(chars, start, end)
+ }
+ }
+ // Create a token array of the right size and return
+ val ret: Array<Token?> = arrayOfNulls<Token>(tokenCount)
+ System.arraycopy(tokens, 0, ret, 0, tokenCount)
+ return ret
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.LevenshteinSuggestionFormatter"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursor.java b/src/com/android/quicksearchbox/ListSuggestionCursor.java
deleted file mode 100644
index 863be31..0000000
--- a/src/com/android/quicksearchbox/ListSuggestionCursor.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.database.DataSetObservable;
-import android.database.DataSetObserver;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * A SuggestionCursor that is backed by a list of Suggestions.
- */
-public class ListSuggestionCursor extends AbstractSuggestionCursorWrapper {
-
- private static final int DEFAULT_CAPACITY = 16;
-
- private final DataSetObservable mDataSetObservable = new DataSetObservable();
-
- private final ArrayList<Entry> mSuggestions;
-
- private HashSet<String> mExtraColumns;
-
- private int mPos = 0;
-
- public ListSuggestionCursor(String userQuery) {
- this(userQuery, DEFAULT_CAPACITY);
- }
-
- @VisibleForTesting
- public ListSuggestionCursor(String userQuery, Suggestion...suggestions) {
- this(userQuery, suggestions.length);
- for (Suggestion suggestion : suggestions) {
- add(suggestion);
- }
- }
-
- public ListSuggestionCursor(String userQuery, int capacity) {
- super(userQuery);
- mSuggestions = new ArrayList<Entry>(capacity);
- }
-
- /**
- * Adds a suggestion from another suggestion cursor.
- *
- * @return {@code true} if the suggestion was added.
- */
- public boolean add(Suggestion suggestion) {
- mSuggestions.add(new Entry(suggestion));
- return true;
- }
-
- public void close() {
- mSuggestions.clear();
- }
-
- public int getPosition() {
- return mPos;
- }
-
- public void moveTo(int pos) {
- mPos = pos;
- }
-
- public boolean moveToNext() {
- int size = mSuggestions.size();
- if (mPos >= size) {
- // Already past the end
- return false;
- }
- mPos++;
- return mPos < size;
- }
-
- public void removeRow() {
- mSuggestions.remove(mPos);
- }
-
- public void replaceRow(Suggestion suggestion) {
- mSuggestions.set(mPos, new Entry(suggestion));
- }
-
- public int getCount() {
- return mSuggestions.size();
- }
-
- @Override
- protected Suggestion current() {
- return mSuggestions.get(mPos).get();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "{[" + getUserQuery() + "] " + mSuggestions + "}";
- }
-
- /**
- * Register an observer that is called when changes happen to this data set.
- *
- * @param observer gets notified when the data set changes.
- */
- public void registerDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.registerObserver(observer);
- }
-
- /**
- * Unregister an observer that has previously been registered with
- * {@link #registerDataSetObserver(DataSetObserver)}
- *
- * @param observer the observer to unregister.
- */
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.unregisterObserver(observer);
- }
-
- protected void notifyDataSetChanged() {
- mDataSetObservable.notifyChanged();
- }
-
- @Override
- public SuggestionExtras getExtras() {
- // override with caching to avoid re-parsing the extras
- return mSuggestions.get(mPos).getExtras();
- }
-
- public Collection<String> getExtraColumns() {
- if (mExtraColumns == null) {
- mExtraColumns = new HashSet<String>();
- for (Entry e : mSuggestions) {
- SuggestionExtras extras = e.getExtras();
- Collection<String> extraColumns = extras == null ? null
- : extras.getExtraColumnNames();
- if (extraColumns != null) {
- for (String column : extras.getExtraColumnNames()) {
- mExtraColumns.add(column);
- }
- }
- }
- }
- return mExtraColumns.isEmpty() ? null : mExtraColumns;
- }
-
- /**
- * This class exists purely to cache the suggestion extras.
- */
- private static class Entry {
- private final Suggestion mSuggestion;
- private SuggestionExtras mExtras;
- public Entry(Suggestion s) {
- mSuggestion = s;
- }
- public Suggestion get() {
- return mSuggestion;
- }
- public SuggestionExtras getExtras() {
- if (mExtras == null) {
- mExtras = mSuggestion.getExtras();
- }
- return mExtras;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursor.kt b/src/com/android/quicksearchbox/ListSuggestionCursor.kt
new file mode 100644
index 0000000..83f12a9
--- /dev/null
+++ b/src/com/android/quicksearchbox/ListSuggestionCursor.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.database.DataSetObservable
+import android.database.DataSetObserver
+import com.google.common.annotations.VisibleForTesting
+import kotlin.collections.ArrayList
+import kotlin.collections.HashSet
+
+/** A SuggestionCursor that is backed by a list of Suggestions. */
+open class ListSuggestionCursor(userQuery: String?, capacity: Int) :
+ AbstractSuggestionCursorWrapper(userQuery!!) {
+ private val mDataSetObservable: DataSetObservable = DataSetObservable()
+
+ private val mSuggestions: ArrayList<Entry>
+
+ private var mExtraColumns: HashSet<String>? = null
+
+ override var position = 0
+
+ constructor(userQuery: String?) : this(userQuery, DEFAULT_CAPACITY)
+
+ @VisibleForTesting
+ constructor(
+ userQuery: String?,
+ vararg suggestions: Suggestion?
+ ) : this(userQuery, suggestions.size) {
+ for (suggestion in suggestions) {
+ add(suggestion!!)
+ }
+ }
+
+ /**
+ * Adds a suggestion from another suggestion cursor.
+ *
+ * @return `true` if the suggestion was added.
+ */
+ open fun add(suggestion: Suggestion): Boolean {
+ mSuggestions.add(Entry(suggestion))
+ return true
+ }
+
+ override fun close() {
+ mSuggestions.clear()
+ }
+
+ override fun moveTo(pos: Int) {
+ position = pos
+ }
+
+ override fun moveToNext(): Boolean {
+ val size: Int = mSuggestions.size
+ if (position >= size) {
+ // Already past the end
+ return false
+ }
+ position++
+ return position < size
+ }
+
+ fun removeRow() {
+ mSuggestions.removeAt(position)
+ }
+
+ fun replaceRow(suggestion: Suggestion) {
+ mSuggestions.set(position, Entry(suggestion))
+ }
+
+ override val count: Int
+ get() = mSuggestions.size
+
+ @Override
+ override fun current(): Suggestion {
+ return mSuggestions.get(position).get()
+ }
+
+ @Override
+ override fun toString(): String {
+ return this::class.simpleName.toString() + "{[" + userQuery + "] " + mSuggestions + "}"
+ }
+
+ /**
+ * Register an observer that is called when changes happen to this data set.
+ *
+ * @param observer gets notified when the data set changes.
+ */
+ override fun registerDataSetObserver(observer: DataSetObserver?) {
+ mDataSetObservable.registerObserver(observer)
+ }
+
+ /**
+ * Unregister an observer that has previously been registered with [.registerDataSetObserver]
+ *
+ * @param observer the observer to unregister.
+ */
+ override fun unregisterDataSetObserver(observer: DataSetObserver?) {
+ mDataSetObservable.unregisterObserver(observer)
+ }
+
+ protected fun notifyDataSetChanged() {
+ mDataSetObservable.notifyChanged()
+ }
+
+ // override with caching to avoid re-parsing the extras
+ @get:Override
+ override val extras: SuggestionExtras?
+ // override with caching to avoid re-parsing the extras
+ get() = mSuggestions.get(position).getExtras()
+
+ override val extraColumns: Collection<String>?
+ get() {
+ if (mExtraColumns == null) {
+ mExtraColumns = HashSet<String>()
+ for (e in mSuggestions) {
+ val extras: SuggestionExtras? = e.getExtras()
+ val extraColumns: Collection<String>? =
+ if (extras == null) null else extras.extraColumnNames
+ if (extraColumns != null) {
+ for (column in extras!!.extraColumnNames) {
+ mExtraColumns?.add(column)
+ }
+ }
+ }
+ }
+ return if (mExtraColumns!!.isEmpty()) null else mExtraColumns
+ }
+
+ /** This class exists purely to cache the suggestion extras. */
+ private class Entry(private val mSuggestion: Suggestion) {
+ private var mExtras: SuggestionExtras? = null
+ fun get(): Suggestion {
+ return mSuggestion
+ }
+
+ fun getExtras(): SuggestionExtras? {
+ if (mExtras == null) {
+ mExtras = mSuggestion.extras
+ }
+ return mExtras
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_CAPACITY = 16
+ }
+
+ init {
+ mSuggestions = ArrayList<Entry>(capacity)
+ }
+}
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java b/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java
deleted file mode 100644
index 48c302c..0000000
--- a/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.util.Log;
-
-import java.util.HashSet;
-
-/**
- * A SuggestionCursor that is backed by a list of SuggestionPosition objects
- * and doesn't allow duplicate suggestions.
- */
-public class ListSuggestionCursorNoDuplicates extends ListSuggestionCursor {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.ListSuggestionCursorNoDuplicates";
-
- private final HashSet<String> mSuggestionKeys;
-
- public ListSuggestionCursorNoDuplicates(String userQuery) {
- super(userQuery);
- mSuggestionKeys = new HashSet<String>();
- }
-
- @Override
- public boolean add(Suggestion suggestion) {
- String key = SuggestionUtils.getSuggestionKey(suggestion);
- if (mSuggestionKeys.add(key)) {
- return super.add(suggestion);
- } else {
- if (DBG) Log.d(TAG, "Rejecting duplicate " + key);
- return false;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.kt b/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.kt
new file mode 100644
index 0000000..2d2ca6c
--- /dev/null
+++ b/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.util.Log
+
+/**
+ * A SuggestionCursor that is backed by a list of SuggestionPosition objects and doesn't allow
+ * duplicate suggestions.
+ */
+class ListSuggestionCursorNoDuplicates(userQuery: String?) : ListSuggestionCursor(userQuery) {
+ private val mSuggestionKeys: HashSet<String>
+
+ @Override
+ override fun add(suggestion: Suggestion): Boolean {
+ val key = SuggestionUtils.getSuggestionKey(suggestion)
+ return if (mSuggestionKeys.add(key)) {
+ super.add(suggestion)
+ } else {
+ if (DBG) Log.d(TAG, "Rejecting duplicate $key")
+ false
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.ListSuggestionCursorNoDuplicates"
+ }
+
+ init {
+ mSuggestionKeys = HashSet<String>()
+ }
+}
diff --git a/src/com/android/quicksearchbox/Logger.java b/src/com/android/quicksearchbox/Logger.java
deleted file mode 100644
index 40ff606..0000000
--- a/src/com/android/quicksearchbox/Logger.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-
-
-/**
- * Interface for logging implementations.
- */
-public interface Logger {
-
- public static final int SEARCH_METHOD_BUTTON = 0;
- public static final int SEARCH_METHOD_KEYBOARD = 1;
-
- public static final int SUGGESTION_CLICK_TYPE_LAUNCH = 0;
- public static final int SUGGESTION_CLICK_TYPE_REFINE = 1;
- public static final int SUGGESTION_CLICK_TYPE_QUICK_CONTACT = 2;
-
- /**
- * Called when QSB has started.
- *
- * @param latency User-visible start-up latency in milliseconds.
- */
- void logStart(int onCreateLatency, int latency, String intentSource);
-
- /**
- * Called when a suggestion is clicked.
- *
- * @param suggestionId Suggestion ID; 0-based position of the suggestion in the UI if the list
- * is flat.
- * @param suggestionCursor all the suggestions shown in the UI.
- * @param clickType One of the SUGGESTION_CLICK_TYPE constants.
- */
- void logSuggestionClick(long suggestionId, SuggestionCursor suggestionCursor, int clickType);
-
- /**
- * The user launched a search.
- *
- * @param startMethod One of {@link #SEARCH_METHOD_BUTTON} or {@link #SEARCH_METHOD_KEYBOARD}.
- * @param numChars The number of characters in the query.
- */
- void logSearch(int startMethod, int numChars);
-
- /**
- * The user launched a voice search.
- */
- void logVoiceSearch();
-
- /**
- * The user left QSB without performing any action (click suggestions, search or voice search).
- *
- * @param suggestionCursor all the suggestions shown in the UI when the user left
- * @param numChars The number of characters in the query typed when the user left.
- */
- void logExit(SuggestionCursor suggestionCursor, int numChars);
-
- /**
- * Logs the latency of a suggestion query to a specific source.
- *
- * @param result The result of the query.
- */
- void logLatency(SourceResult result);
-
-}
diff --git a/src/com/android/quicksearchbox/Logger.kt b/src/com/android/quicksearchbox/Logger.kt
new file mode 100644
index 0000000..8de5ce7
--- /dev/null
+++ b/src/com/android/quicksearchbox/Logger.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** Interface for logging implementations. */
+interface Logger {
+ /**
+ * Called when QSB has started.
+ *
+ * @param latency User-visible start-up latency in milliseconds.
+ */
+ fun logStart(onCreateLatency: Int, latency: Int, intentSource: String?)
+
+ /**
+ * Called when a suggestion is clicked.
+ *
+ * @param suggestionId Suggestion ID; 0-based position of the suggestion in the UI if the list is
+ * flat.
+ * @param suggestionCursor all the suggestions shown in the UI.
+ * @param clickType One of the SUGGESTION_CLICK_TYPE constants.
+ */
+ fun logSuggestionClick(suggestionId: Long, suggestionCursor: SuggestionCursor?, clickType: Int)
+
+ /**
+ * The user launched a search.
+ *
+ * @param startMethod One of [.SEARCH_METHOD_BUTTON] or [.SEARCH_METHOD_KEYBOARD].
+ * @param numChars The number of characters in the query.
+ */
+ fun logSearch(startMethod: Int, numChars: Int)
+
+ /** The user launched a voice search. */
+ fun logVoiceSearch()
+
+ /**
+ * The user left QSB without performing any action (click suggestions, search or voice search).
+ *
+ * @param suggestionCursor all the suggestions shown in the UI when the user left
+ * @param numChars The number of characters in the query typed when the user left.
+ */
+ fun logExit(suggestionCursor: SuggestionCursor?, numChars: Int)
+
+ /**
+ * Logs the latency of a suggestion query to a specific source.
+ *
+ * @param result The result of the query.
+ */
+ fun logLatency(result: SourceResult?)
+
+ companion object {
+ const val SEARCH_METHOD_BUTTON = 0
+ const val SEARCH_METHOD_KEYBOARD = 1
+ const val SUGGESTION_CLICK_TYPE_LAUNCH = 0
+ const val SUGGESTION_CLICK_TYPE_REFINE = 1
+ const val SUGGESTION_CLICK_TYPE_QUICK_CONTACT = 2
+ }
+}
diff --git a/src/com/android/quicksearchbox/PackageIconLoader.java b/src/com/android/quicksearchbox/PackageIconLoader.java
deleted file mode 100644
index a572d3c..0000000
--- a/src/com/android/quicksearchbox/PackageIconLoader.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.CachedLater;
-import com.android.quicksearchbox.util.NamedTask;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-import com.android.quicksearchbox.util.Now;
-import com.android.quicksearchbox.util.NowOrLater;
-import com.android.quicksearchbox.util.Util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Loads icons from other packages.
- *
- * Code partly stolen from {@link ContentResolver} and android.app.SuggestionsAdapter.
- */
-public class PackageIconLoader implements IconLoader {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.PackageIconLoader";
-
- private final Context mContext;
-
- private final String mPackageName;
-
- private Context mPackageContext;
-
- private final Handler mUiThread;
-
- private final NamedTaskExecutor mIconLoaderExecutor;
-
- /**
- * Creates a new icon loader.
- *
- * @param context The QSB application context.
- * @param packageName The name of the package from which the icons will be loaded.
- * Resource IDs without an explicit package will be resolved against the package
- * of this context.
- */
- public PackageIconLoader(Context context, String packageName, Handler uiThread,
- NamedTaskExecutor iconLoaderExecutor) {
- mContext = context;
- mPackageName = packageName;
- mUiThread = uiThread;
- mIconLoaderExecutor = iconLoaderExecutor;
- }
-
- private boolean ensurePackageContext() {
- if (mPackageContext == null) {
- try {
- mPackageContext = mContext.createPackageContext(mPackageName,
- Context.CONTEXT_RESTRICTED);
- } catch (PackageManager.NameNotFoundException ex) {
- // This should only happen if the app has just be uninstalled
- Log.e(TAG, "Application not found " + mPackageName);
- return false;
- }
- }
- return true;
- }
-
- public NowOrLater<Drawable> getIcon(final String drawableId) {
- if (DBG) Log.d(TAG, "getIcon(" + drawableId + ")");
- if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
- return new Now<Drawable>(null);
- }
- if (!ensurePackageContext()) {
- return new Now<Drawable>(null);
- }
- NowOrLater<Drawable> drawable;
- try {
- // First, see if it's just an integer
- int resourceId = Integer.parseInt(drawableId);
- // If so, find it by resource ID
- Drawable icon = mPackageContext.getResources().getDrawable(resourceId);
- drawable = new Now<Drawable>(icon);
- } catch (NumberFormatException nfe) {
- // It's not an integer, use it as a URI
- Uri uri = Uri.parse(drawableId);
- if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
- // load all resources synchronously, to reduce UI flickering
- drawable = new Now<Drawable>(getDrawable(uri));
- } else {
- drawable = new IconLaterTask(uri);
- }
- } catch (Resources.NotFoundException nfe) {
- // It was an integer, but it couldn't be found, bail out
- Log.w(TAG, "Icon resource not found: " + drawableId);
- drawable = new Now<Drawable>(null);
- }
- return drawable;
- }
-
- public Uri getIconUri(String drawableId) {
- if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
- return null;
- }
- if (!ensurePackageContext()) return null;
- try {
- int resourceId = Integer.parseInt(drawableId);
- return Util.getResourceUri(mPackageContext, resourceId);
- } catch (NumberFormatException nfe) {
- return Uri.parse(drawableId);
- }
- }
-
- /**
- * Gets a drawable by URI.
- *
- * @return A drawable, or {@code null} if the drawable could not be loaded.
- */
- private Drawable getDrawable(Uri uri) {
- try {
- String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- // Load drawables through Resources, to get the source density information
- OpenResourceIdResult r = getResourceId(uri);
- try {
- return r.r.getDrawable(r.id);
- } catch (Resources.NotFoundException ex) {
- throw new FileNotFoundException("Resource does not exist: " + uri);
- }
- } else {
- // Let the ContentResolver handle content and file URIs.
- InputStream stream = mPackageContext.getContentResolver().openInputStream(uri);
- if (stream == null) {
- throw new FileNotFoundException("Failed to open " + uri);
- }
- try {
- return Drawable.createFromStream(stream, null);
- } finally {
- try {
- stream.close();
- } catch (IOException ex) {
- Log.e(TAG, "Error closing icon stream for " + uri, ex);
- }
- }
- }
- } catch (FileNotFoundException fnfe) {
- Log.w(TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
- return null;
- }
- }
-
- /**
- * A resource identified by the {@link Resources} that contains it, and a resource id.
- */
- private class OpenResourceIdResult {
- public Resources r;
- public int id;
- }
-
- /**
- * Resolves an android.resource URI to a {@link Resources} and a resource id.
- */
- private OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
- String authority = uri.getAuthority();
- Resources r;
- if (TextUtils.isEmpty(authority)) {
- throw new FileNotFoundException("No authority: " + uri);
- } else {
- try {
- r = mPackageContext.getPackageManager().getResourcesForApplication(authority);
- } catch (NameNotFoundException ex) {
- throw new FileNotFoundException("Failed to get resources: " + ex);
- }
- }
- List<String> path = uri.getPathSegments();
- if (path == null) {
- throw new FileNotFoundException("No path: " + uri);
- }
- int len = path.size();
- int id;
- if (len == 1) {
- try {
- id = Integer.parseInt(path.get(0));
- } catch (NumberFormatException e) {
- throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
- }
- } else if (len == 2) {
- id = r.getIdentifier(path.get(1), path.get(0), authority);
- } else {
- throw new FileNotFoundException("More than two path segments: " + uri);
- }
- if (id == 0) {
- throw new FileNotFoundException("No resource found for: " + uri);
- }
- OpenResourceIdResult res = new OpenResourceIdResult();
- res.r = r;
- res.id = id;
- return res;
- }
-
- private class IconLaterTask extends CachedLater<Drawable> implements NamedTask {
- private final Uri mUri;
-
- public IconLaterTask(Uri iconUri) {
- mUri = iconUri;
- }
-
- @Override
- protected void create() {
- mIconLoaderExecutor.execute(this);
- }
-
- @Override
- public void run() {
- final Drawable icon = getIcon();
- mUiThread.post(new Runnable(){
- public void run() {
- store(icon);
- }});
- }
-
- @Override
- public String getName() {
- return mPackageName;
- }
-
- private Drawable getIcon() {
- try {
- return getDrawable(mUri);
- } catch (Throwable t) {
- // we're making a call into another package, which could throw any exception.
- // Make sure it doesn't crash QSB
- Log.e(TAG, "Failed to load icon " + mUri, t);
- return null;
- }
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/PackageIconLoader.kt b/src/com/android/quicksearchbox/PackageIconLoader.kt
new file mode 100644
index 0000000..530d1b1
--- /dev/null
+++ b/src/com/android/quicksearchbox/PackageIconLoader.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.text.TextUtils
+import android.util.Log
+import androidx.core.content.ContextCompat
+import com.android.quicksearchbox.util.*
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.io.InputStream
+
+/**
+ * Loads icons from other packages.
+ *
+ * Code partly stolen from [ContentResolver] and android.app.SuggestionsAdapter.
+ */
+class PackageIconLoader(
+ context: Context?,
+ packageName: String?,
+ uiThread: Handler?,
+ iconLoaderExecutor: NamedTaskExecutor
+) : IconLoader {
+
+ private val mContext: Context?
+
+ private val mPackageName: String?
+
+ private var mPackageContext: Context? = null
+
+ private val mUiThread: Handler?
+
+ private val mIconLoaderExecutor: NamedTaskExecutor
+
+ private fun ensurePackageContext(): Boolean {
+ if (mPackageContext == null) {
+ mPackageContext =
+ try {
+ mContext?.createPackageContext(mPackageName, Context.CONTEXT_RESTRICTED)
+ } catch (ex: PackageManager.NameNotFoundException) {
+ // This should only happen if the app has just be uninstalled
+ Log.e(TAG, "Application not found " + mPackageName)
+ return false
+ }
+ }
+ return true
+ }
+
+ override fun getIcon(drawableId: String?): NowOrLater<Drawable?>? {
+ if (DBG) Log.d(TAG, "getIcon($drawableId)")
+ if (TextUtils.isEmpty(drawableId) || "0" == drawableId) {
+ return Now<Drawable>(null)
+ }
+ if (!ensurePackageContext()) {
+ return Now<Drawable>(null)
+ }
+ var drawable: NowOrLater<Drawable?>?
+ try {
+ // First, see if it's just an integer
+ val resourceId: Int = drawableId!!.toInt()
+ // If so, find it by resource ID
+ val icon: Drawable? = ContextCompat.getDrawable(mPackageContext!!, resourceId)
+ drawable = Now(icon)
+ } catch (nfe: NumberFormatException) {
+ // It's not an integer, use it as a URI
+ val uri: Uri = Uri.parse(drawableId)
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
+ // load all resources synchronously, to reduce UI flickering
+ drawable = Now(getDrawable(uri))
+ } else {
+ drawable = IconLaterTask(uri)
+ }
+ } catch (nfe: Resources.NotFoundException) {
+ // It was an integer, but it couldn't be found, bail out
+ Log.w(TAG, "Icon resource not found: $drawableId")
+ drawable = Now(null)
+ }
+ return drawable
+ }
+
+ override fun getIconUri(drawableId: String?): Uri? {
+ if (TextUtils.isEmpty(drawableId) || "0" == drawableId) {
+ return null
+ }
+ return if (!ensurePackageContext()) null
+ else
+ try {
+ val resourceId: Int = drawableId!!.toInt()
+ Util.getResourceUri(mPackageContext, resourceId)
+ } catch (nfe: NumberFormatException) {
+ Uri.parse(drawableId)
+ }
+ }
+
+ /**
+ * Gets a drawable by URI.
+ *
+ * @return A drawable, or `null` if the drawable could not be loaded.
+ */
+ private fun getDrawable(uri: Uri): Drawable? {
+ return try {
+ val scheme: String? = uri.getScheme()
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ // Load drawables through Resources, to get the source density information
+ val r: OpenResourceIdResult = getResourceId(uri)
+ try {
+ ContextCompat.getDrawable(mPackageContext!!, r.id)
+ } catch (ex: Resources.NotFoundException) {
+ throw FileNotFoundException("Resource does not exist: $uri")
+ }
+ } else {
+ // Let the ContentResolver handle content and file URIs.
+ val stream: InputStream =
+ mPackageContext!!.getContentResolver().openInputStream(uri)
+ ?: throw FileNotFoundException("Failed to open $uri")
+ try {
+ Drawable.createFromStream(stream, null)
+ } finally {
+ try {
+ stream.close()
+ } catch (ex: IOException) {
+ Log.e(TAG, "Error closing icon stream for $uri", ex)
+ }
+ }
+ }
+ } catch (fnfe: FileNotFoundException) {
+ Log.w(TAG, "Icon not found: " + uri + ", " + fnfe.message)
+ null
+ }
+ }
+
+ /** A resource identified by the [Resources] that contains it, and a resource id. */
+ private inner class OpenResourceIdResult {
+ @JvmField var r: Resources? = null
+
+ @JvmField var id = 0
+ }
+
+ /** Resolves an android.resource URI to a [Resources] and a resource id. */
+ @Throws(FileNotFoundException::class)
+ private fun getResourceId(uri: Uri): OpenResourceIdResult {
+ val authority: String? = uri.getAuthority()
+ val r: Resources? =
+ if (TextUtils.isEmpty(authority)) {
+ throw FileNotFoundException("No authority: $uri")
+ } else {
+ try {
+ mPackageContext?.getPackageManager()?.getResourcesForApplication(authority!!)
+ } catch (ex: NameNotFoundException) {
+ throw FileNotFoundException("Failed to get resources: $ex")
+ }
+ }
+ val path: List<String> = uri.getPathSegments() ?: throw FileNotFoundException("No path: $uri")
+ val id: Int =
+ when (path.size) {
+ 1 -> {
+ try {
+ Integer.parseInt(path[0])
+ } catch (e: NumberFormatException) {
+ throw FileNotFoundException("Single path segment is not a resource ID: $uri")
+ }
+ }
+ 2 -> {
+ r!!.getIdentifier(path[1], path[0], authority)
+ }
+ else -> {
+ throw FileNotFoundException("More than two path segments: $uri")
+ }
+ }
+ if (id == 0) {
+ throw FileNotFoundException("No resource found for: $uri")
+ }
+ val res = OpenResourceIdResult()
+ res.r = r
+ res.id = id
+ return res
+ }
+
+ private inner class IconLaterTask(iconUri: Uri) : CachedLater<Drawable?>(), NamedTask {
+ private val mUri: Uri
+
+ @Override
+ override fun create() {
+ mIconLoaderExecutor.execute(this)
+ }
+
+ @Override
+ override fun run() {
+ val icon: Drawable? = icon
+ mUiThread?.post(
+ object : Runnable {
+ override fun run() {
+ store(icon)
+ }
+ }
+ )
+ }
+
+ @get:Override
+ override val name: String?
+ get() = mPackageName
+
+ // we're making a call into another package, which could throw any exception.
+ // Make sure it doesn't crash QSB
+ private val icon: Drawable?
+ get() =
+ try {
+ getDrawable(mUri)
+ } catch (t: Throwable) {
+ // we're making a call into another package, which could throw any exception.
+ // Make sure it doesn't crash QSB
+ Log.e(TAG, "Failed to load icon $mUri", t)
+ null
+ }
+
+ init {
+ mUri = iconUri
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.PackageIconLoader"
+ }
+
+ /**
+ * Creates a new icon loader.
+ *
+ * @param context The QSB application context.
+ * @param packageName The name of the package from which the icons will be loaded.
+ * ```
+ * Resource IDs without an explicit package will be resolved against the package
+ * of this context.
+ * ```
+ */
+ init {
+ mContext = context
+ mPackageName = packageName
+ mUiThread = uiThread
+ mIconLoaderExecutor = iconLoaderExecutor
+ }
+}
diff --git a/src/com/android/quicksearchbox/QsbApplication.java b/src/com/android/quicksearchbox/QsbApplication.java
deleted file mode 100644
index b3bccd3..0000000
--- a/src/com/android/quicksearchbox/QsbApplication.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.view.ContextThemeWrapper;
-
-import com.android.quicksearchbox.google.GoogleSource;
-import com.android.quicksearchbox.google.GoogleSuggestClient;
-import com.android.quicksearchbox.google.SearchBaseUrlHelper;
-import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory;
-import com.android.quicksearchbox.ui.SuggestionViewFactory;
-import com.android.quicksearchbox.util.Factory;
-import com.android.quicksearchbox.util.HttpHelper;
-import com.android.quicksearchbox.util.JavaNetHttpHelper;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-import com.android.quicksearchbox.util.PerNameExecutor;
-import com.android.quicksearchbox.util.PriorityThreadFactory;
-import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-public class QsbApplication {
- private final Context mContext;
-
- private int mVersionCode;
- private Handler mUiThreadHandler;
- private Config mConfig;
- private SearchSettings mSettings;
- private NamedTaskExecutor mSourceTaskExecutor;
- private ThreadFactory mQueryThreadFactory;
- private SuggestionsProvider mSuggestionsProvider;
- private SuggestionViewFactory mSuggestionViewFactory;
- private GoogleSource mGoogleSource;
- private VoiceSearch mVoiceSearch;
- private Logger mLogger;
- private SuggestionFormatter mSuggestionFormatter;
- private TextAppearanceFactory mTextAppearanceFactory;
- private NamedTaskExecutor mIconLoaderExecutor;
- private HttpHelper mHttpHelper;
- private SearchBaseUrlHelper mSearchBaseUrlHelper;
-
- public QsbApplication(Context context) {
- // the application context does not use the theme from the <application> tag
- mContext = new ContextThemeWrapper(context, R.style.Theme_QuickSearchBox);
- }
-
- public static boolean isFroyoOrLater() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
- }
-
- public static boolean isHoneycombOrLater() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
- }
-
- public static QsbApplication get(Context context) {
- return ((QsbApplicationWrapper) context.getApplicationContext()).getApp();
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- public int getVersionCode() {
- if (mVersionCode == 0) {
- try {
- PackageManager pm = getContext().getPackageManager();
- PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0);
- mVersionCode = pkgInfo.versionCode;
- } catch (PackageManager.NameNotFoundException ex) {
- // The current package should always exist, how else could we
- // run code from it?
- throw new RuntimeException(ex);
- }
- }
- return mVersionCode;
- }
-
- protected void checkThread() {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new IllegalStateException("Accessed Application object from thread "
- + Thread.currentThread().getName());
- }
- }
-
- protected void close() {
- checkThread();
- if (mConfig != null) {
- mConfig.close();
- mConfig = null;
- }
- if (mSuggestionsProvider != null) {
- mSuggestionsProvider.close();
- mSuggestionsProvider = null;
- }
- }
-
- public synchronized Handler getMainThreadHandler() {
- if (mUiThreadHandler == null) {
- mUiThreadHandler = new Handler(Looper.getMainLooper());
- }
- return mUiThreadHandler;
- }
-
- public void runOnUiThread(Runnable action) {
- getMainThreadHandler().post(action);
- }
-
- public synchronized NamedTaskExecutor getIconLoaderExecutor() {
- if (mIconLoaderExecutor == null) {
- mIconLoaderExecutor = createIconLoaderExecutor();
- }
- return mIconLoaderExecutor;
- }
-
- protected NamedTaskExecutor createIconLoaderExecutor() {
- ThreadFactory iconThreadFactory = new PriorityThreadFactory(
- Process.THREAD_PRIORITY_BACKGROUND);
- return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory));
- }
-
- /**
- * Indicates that construction of the QSB UI is now complete.
- */
- public void onStartupComplete() {
- }
-
- /**
- * Gets the QSB configuration object.
- * May be called from any thread.
- */
- public synchronized Config getConfig() {
- if (mConfig == null) {
- mConfig = createConfig();
- }
- return mConfig;
- }
-
- protected Config createConfig() {
- return new Config(getContext());
- }
-
- public synchronized SearchSettings getSettings() {
- if (mSettings == null) {
- mSettings = createSettings();
- mSettings.upgradeSettingsIfNeeded();
- }
- return mSettings;
- }
-
- protected SearchSettings createSettings() {
- return new SearchSettingsImpl(getContext(), getConfig());
- }
-
- protected Factory<Executor> createExecutorFactory(final int numThreads) {
- final ThreadFactory threadFactory = getQueryThreadFactory();
- return new Factory<Executor>() {
- @Override
- public Executor create() {
- return Executors.newFixedThreadPool(numThreads, threadFactory);
- }
- };
- }
-
- /**
- /**
- * Gets the source task executor.
- * May only be called from the main thread.
- */
- public NamedTaskExecutor getSourceTaskExecutor() {
- checkThread();
- if (mSourceTaskExecutor == null) {
- mSourceTaskExecutor = createSourceTaskExecutor();
- }
- return mSourceTaskExecutor;
- }
-
- protected NamedTaskExecutor createSourceTaskExecutor() {
- ThreadFactory queryThreadFactory = getQueryThreadFactory();
- return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
- }
-
- /**
- * Gets the query thread factory.
- * May only be called from the main thread.
- */
- protected ThreadFactory getQueryThreadFactory() {
- checkThread();
- if (mQueryThreadFactory == null) {
- mQueryThreadFactory = createQueryThreadFactory();
- }
- return mQueryThreadFactory;
- }
-
- protected ThreadFactory createQueryThreadFactory() {
- String nameFormat = "QSB #%d";
- int priority = getConfig().getQueryThreadPriority();
- return new ThreadFactoryBuilder()
- .setNameFormat(nameFormat)
- .setThreadFactory(new PriorityThreadFactory(priority))
- .build();
- }
-
- /**
- * Gets the suggestion provider.
- *
- * May only be called from the main thread.
- */
- protected SuggestionsProvider getSuggestionsProvider() {
- checkThread();
- if (mSuggestionsProvider == null) {
- mSuggestionsProvider = createSuggestionsProvider();
- }
- return mSuggestionsProvider;
- }
-
- protected SuggestionsProvider createSuggestionsProvider() {
- return new SuggestionsProviderImpl(getConfig(),
- getSourceTaskExecutor(),
- getMainThreadHandler(),
- getLogger());
- }
-
- /**
- * Gets the default suggestion view factory.
- * May only be called from the main thread.
- */
- public SuggestionViewFactory getSuggestionViewFactory() {
- checkThread();
- if (mSuggestionViewFactory == null) {
- mSuggestionViewFactory = createSuggestionViewFactory();
- }
- return mSuggestionViewFactory;
- }
-
- protected SuggestionViewFactory createSuggestionViewFactory() {
- return new DefaultSuggestionViewFactory(getContext());
- }
-
- /**
- * Gets the Google source.
- * May only be called from the main thread.
- */
- public GoogleSource getGoogleSource() {
- checkThread();
- if (mGoogleSource == null) {
- mGoogleSource = createGoogleSource();
- }
- return mGoogleSource;
- }
-
- protected GoogleSource createGoogleSource() {
- return new GoogleSuggestClient(getContext(), getMainThreadHandler(),
- getIconLoaderExecutor(), getConfig());
- }
-
- /**
- * Gets Voice Search utilities.
- */
- public VoiceSearch getVoiceSearch() {
- checkThread();
- if (mVoiceSearch == null) {
- mVoiceSearch = createVoiceSearch();
- }
- return mVoiceSearch;
- }
-
- protected VoiceSearch createVoiceSearch() {
- return new VoiceSearch(getContext());
- }
-
- /**
- * Gets the event logger.
- * May only be called from the main thread.
- */
- public Logger getLogger() {
- checkThread();
- if (mLogger == null) {
- mLogger = createLogger();
- }
- return mLogger;
- }
-
- protected Logger createLogger() {
- return new EventLogLogger(getContext(), getConfig());
- }
-
- public SuggestionFormatter getSuggestionFormatter() {
- if (mSuggestionFormatter == null) {
- mSuggestionFormatter = createSuggestionFormatter();
- }
- return mSuggestionFormatter;
- }
-
- protected SuggestionFormatter createSuggestionFormatter() {
- return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
- }
-
- public TextAppearanceFactory getTextAppearanceFactory() {
- if (mTextAppearanceFactory == null) {
- mTextAppearanceFactory = createTextAppearanceFactory();
- }
- return mTextAppearanceFactory;
- }
-
- protected TextAppearanceFactory createTextAppearanceFactory() {
- return new TextAppearanceFactory(getContext());
- }
-
- public synchronized HttpHelper getHttpHelper() {
- if (mHttpHelper == null) {
- mHttpHelper = createHttpHelper();
- }
- return mHttpHelper;
- }
-
- protected HttpHelper createHttpHelper() {
- return new JavaNetHttpHelper(
- new JavaNetHttpHelper.PassThroughRewriter(),
- getConfig().getUserAgent());
- }
-
- public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() {
- if (mSearchBaseUrlHelper == null) {
- mSearchBaseUrlHelper = createSearchBaseUrlHelper();
- }
-
- return mSearchBaseUrlHelper;
- }
-
- protected SearchBaseUrlHelper createSearchBaseUrlHelper() {
- // This cast to "SearchSettingsImpl" is somewhat ugly.
- return new SearchBaseUrlHelper(getContext(), getHttpHelper(),
- getSettings(), ((SearchSettingsImpl)getSettings()).getSearchPreferences());
- }
-
- public Help getHelp() {
- // No point caching this, it's super cheap.
- return new Help(getContext(), getConfig());
- }
-}
diff --git a/src/com/android/quicksearchbox/QsbApplication.kt b/src/com/android/quicksearchbox/QsbApplication.kt
new file mode 100644
index 0000000..f53b481
--- /dev/null
+++ b/src/com/android/quicksearchbox/QsbApplication.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.os.Process
+import android.view.ContextThemeWrapper
+import com.android.quicksearchbox.google.GoogleSource
+import com.android.quicksearchbox.google.GoogleSuggestClient
+import com.android.quicksearchbox.google.SearchBaseUrlHelper
+import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory
+import com.android.quicksearchbox.ui.SuggestionViewFactory
+import com.android.quicksearchbox.util.*
+import com.google.common.util.concurrent.ThreadFactoryBuilder
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+
+class QsbApplication(context: Context?) {
+ private val mContext: Context?
+
+ private var mVersionCode: Long = 0
+ private var mUiThreadHandler: Handler? = null
+ private var mConfig: Config? = null
+ private var mSettings: SearchSettings? = null
+ private var mSourceTaskExecutor: NamedTaskExecutor? = null
+ private var mQueryThreadFactory: ThreadFactory? = null
+ private var mSuggestionsProvider: SuggestionsProvider? = null
+ private var mSuggestionViewFactory: SuggestionViewFactory? = null
+ private var mGoogleSource: GoogleSource? = null
+ private var mVoiceSearch: VoiceSearch? = null
+ private var mLogger: Logger? = null
+ private var mSuggestionFormatter: SuggestionFormatter? = null
+ private var mTextAppearanceFactory: TextAppearanceFactory? = null
+ private var mIconLoaderExecutor: NamedTaskExecutor? = null
+ private var mHttpHelper: HttpHelper? = null
+ private var mSearchBaseUrlHelper: SearchBaseUrlHelper? = null
+ protected val context: Context?
+ get() = mContext
+
+ // The current package should always exist, how else could we
+ // run code from it?
+ val versionCode: Long
+ @Suppress("DEPRECATION")
+ get() {
+ if (mVersionCode == 0L) {
+ mVersionCode =
+ try {
+ val pm: PackageManager? = context?.getPackageManager()
+ val pkgInfo: PackageInfo? = pm?.getPackageInfo(context!!.getPackageName(), 0)
+ pkgInfo!!.getLongVersionCode()
+ } catch (ex: PackageManager.NameNotFoundException) {
+ // The current package should always exist, how else could we
+ // run code from it?
+ throw RuntimeException(ex)
+ }
+ }
+ return mVersionCode
+ }
+
+ protected fun checkThread() {
+ if (Looper.myLooper() !== Looper.getMainLooper()) {
+ throw IllegalStateException(
+ "Accessed Application object from thread " + Thread.currentThread().getName()
+ )
+ }
+ }
+
+ fun close() {
+ checkThread()
+ if (mConfig != null) {
+ mConfig!!.close()
+ mConfig = null
+ }
+ if (mSuggestionsProvider != null) {
+ mSuggestionsProvider!!.close()
+ mSuggestionsProvider = null
+ }
+ }
+
+ @get:Synchronized
+ val mainThreadHandler: Handler?
+ get() {
+ if (mUiThreadHandler == null) {
+ mUiThreadHandler = Handler(Looper.getMainLooper())
+ }
+ return mUiThreadHandler
+ }
+
+ fun runOnUiThread(action: Runnable?) {
+ mainThreadHandler?.post(action!!)
+ }
+
+ @get:Synchronized
+ val iconLoaderExecutor: NamedTaskExecutor?
+ get() {
+ if (mIconLoaderExecutor == null) {
+ mIconLoaderExecutor = createIconLoaderExecutor()
+ }
+ return mIconLoaderExecutor
+ }
+
+ protected fun createIconLoaderExecutor(): NamedTaskExecutor {
+ val iconThreadFactory: ThreadFactory = PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)
+ return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory))
+ }
+
+ /** Indicates that construction of the QSB UI is now complete. */
+ fun onStartupComplete() {}
+
+ /** Gets the QSB configuration object. May be called from any thread. */
+ @get:Synchronized
+ val config: Config?
+ get() {
+ if (mConfig == null) {
+ mConfig = createConfig()
+ }
+ return mConfig
+ }
+
+ protected fun createConfig(): Config {
+ return Config(context)
+ }
+
+ @get:Synchronized
+ val settings: SearchSettings?
+ get() {
+ if (mSettings == null) {
+ mSettings = createSettings()
+ mSettings!!.upgradeSettingsIfNeeded()
+ }
+ return mSettings
+ }
+
+ protected fun createSettings(): SearchSettings {
+ return SearchSettingsImpl(context, config)
+ }
+
+ protected fun createExecutorFactory(numThreads: Int): Factory<Executor?> {
+ val threadFactory: ThreadFactory? = queryThreadFactory
+ return object : Factory<Executor?> {
+ @Override
+ override fun create(): Executor {
+ return Executors.newFixedThreadPool(numThreads, threadFactory)
+ }
+ }
+ }
+
+ /** Gets the source task executor. May only be called from the main thread. */
+ val sourceTaskExecutor: NamedTaskExecutor?
+ get() {
+ checkThread()
+ if (mSourceTaskExecutor == null) {
+ mSourceTaskExecutor = createSourceTaskExecutor()
+ }
+ return mSourceTaskExecutor
+ }
+
+ protected fun createSourceTaskExecutor(): NamedTaskExecutor {
+ val queryThreadFactory: ThreadFactory? = queryThreadFactory
+ return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory))
+ }
+
+ /** Gets the query thread factory. May only be called from the main thread. */
+ protected val queryThreadFactory: ThreadFactory?
+ get() {
+ checkThread()
+ if (mQueryThreadFactory == null) {
+ mQueryThreadFactory = createQueryThreadFactory()
+ }
+ return mQueryThreadFactory
+ }
+
+ protected fun createQueryThreadFactory(): ThreadFactory {
+ val nameFormat = "QSB #%d"
+ val priority: Int = config!!.queryThreadPriority
+ return ThreadFactoryBuilder()
+ .setNameFormat(nameFormat)
+ .setThreadFactory(PriorityThreadFactory(priority))
+ .build()
+ }
+
+ /**
+ * Gets the suggestion provider.
+ *
+ * May only be called from the main thread.
+ */
+ val suggestionsProvider: SuggestionsProvider?
+ get() {
+ checkThread()
+ if (mSuggestionsProvider == null) {
+ mSuggestionsProvider = createSuggestionsProvider()
+ }
+ return mSuggestionsProvider
+ }
+
+ protected fun createSuggestionsProvider(): SuggestionsProvider {
+ return SuggestionsProviderImpl(config!!, sourceTaskExecutor!!, mainThreadHandler, logger)
+ }
+
+ /** Gets the default suggestion view factory. May only be called from the main thread. */
+ val suggestionViewFactory: SuggestionViewFactory?
+ get() {
+ checkThread()
+ if (mSuggestionViewFactory == null) {
+ mSuggestionViewFactory = createSuggestionViewFactory()
+ }
+ return mSuggestionViewFactory
+ }
+
+ protected fun createSuggestionViewFactory(): SuggestionViewFactory {
+ return DefaultSuggestionViewFactory(context)
+ }
+
+ /** Gets the Google source. May only be called from the main thread. */
+ val googleSource: GoogleSource?
+ get() {
+ checkThread()
+ if (mGoogleSource == null) {
+ mGoogleSource = createGoogleSource()
+ }
+ return mGoogleSource
+ }
+
+ protected fun createGoogleSource(): GoogleSource {
+ return GoogleSuggestClient(context, mainThreadHandler, iconLoaderExecutor!!, config!!)
+ }
+
+ /** Gets Voice Search utilities. */
+ val voiceSearch: VoiceSearch?
+ get() {
+ checkThread()
+ if (mVoiceSearch == null) {
+ mVoiceSearch = createVoiceSearch()
+ }
+ return mVoiceSearch
+ }
+
+ protected fun createVoiceSearch(): VoiceSearch {
+ return VoiceSearch(context)
+ }
+
+ /** Gets the event logger. May only be called from the main thread. */
+ val logger: Logger?
+ get() {
+ checkThread()
+ if (mLogger == null) {
+ mLogger = createLogger()
+ }
+ return mLogger
+ }
+
+ protected fun createLogger(): Logger {
+ return EventLogLogger(context, config!!)
+ }
+
+ val suggestionFormatter: SuggestionFormatter?
+ get() {
+ if (mSuggestionFormatter == null) {
+ mSuggestionFormatter = createSuggestionFormatter()
+ }
+ return mSuggestionFormatter
+ }
+
+ protected fun createSuggestionFormatter(): SuggestionFormatter {
+ return LevenshteinSuggestionFormatter(textAppearanceFactory)
+ }
+
+ val textAppearanceFactory: TextAppearanceFactory?
+ get() {
+ if (mTextAppearanceFactory == null) {
+ mTextAppearanceFactory = createTextAppearanceFactory()
+ }
+ return mTextAppearanceFactory
+ }
+
+ protected fun createTextAppearanceFactory(): TextAppearanceFactory {
+ return TextAppearanceFactory(context)
+ }
+
+ @get:Synchronized
+ val httpHelper: HttpHelper?
+ get() {
+ if (mHttpHelper == null) {
+ mHttpHelper = createHttpHelper()
+ }
+ return mHttpHelper
+ }
+
+ protected fun createHttpHelper(): HttpHelper {
+ return JavaNetHttpHelper(JavaNetHttpHelper.PassThroughRewriter(), config!!.userAgent)
+ }
+
+ @get:Synchronized
+ val searchBaseUrlHelper: SearchBaseUrlHelper?
+ get() {
+ if (mSearchBaseUrlHelper == null) {
+ mSearchBaseUrlHelper = createSearchBaseUrlHelper()
+ }
+ return mSearchBaseUrlHelper
+ }
+
+ protected fun createSearchBaseUrlHelper(): SearchBaseUrlHelper {
+ // This cast to "SearchSettingsImpl" is somewhat ugly.
+ return SearchBaseUrlHelper(
+ context,
+ httpHelper!!,
+ settings!!,
+ (settings as SearchSettingsImpl?)!!.searchPreferences
+ )
+ }
+
+ // No point caching this, it's super cheap.
+ val help: Help
+ get() = // No point caching this, it's super cheap.
+ Help(context, config!!)
+
+ companion object {
+ val isFroyoOrLater: Boolean
+ get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO
+ val isHoneycombOrLater: Boolean
+ get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
+
+ @JvmStatic
+ operator fun get(context: Context?): QsbApplication {
+ return (context?.getApplicationContext() as QsbApplicationWrapper).app
+ }
+ }
+
+ init {
+ // the application context does not use the theme from the <application> tag
+ mContext = ContextThemeWrapper(context, R.style.Theme_QuickSearchBox)
+ }
+}
diff --git a/src/com/android/quicksearchbox/QsbApplicationWrapper.java b/src/com/android/quicksearchbox/QsbApplicationWrapper.java
deleted file mode 100644
index 7329cdf..0000000
--- a/src/com/android/quicksearchbox/QsbApplicationWrapper.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.Application;
-
-public class QsbApplicationWrapper extends Application {
-
- private QsbApplication mApp;
-
- @Override
- public void onTerminate() {
- synchronized (this) {
- if (mApp != null) {
- mApp.close();
- }
- }
- super.onTerminate();
- }
-
- public synchronized QsbApplication getApp() {
- if (mApp == null) {
- mApp = createQsbApplication();
- }
- return mApp;
- }
-
- protected QsbApplication createQsbApplication() {
- return new QsbApplication(this);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/QsbApplicationWrapper.kt b/src/com/android/quicksearchbox/QsbApplicationWrapper.kt
new file mode 100644
index 0000000..fedae95
--- /dev/null
+++ b/src/com/android/quicksearchbox/QsbApplicationWrapper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.Application
+
+class QsbApplicationWrapper : Application() {
+
+ private var mApp: QsbApplication? = null
+
+ @Override
+ override fun onTerminate() {
+ synchronized(this) {
+ if (mApp != null) {
+ mApp!!.close()
+ }
+ }
+ super.onTerminate()
+ }
+
+ @get:Synchronized
+ val app: QsbApplication
+ get() {
+ if (mApp == null) {
+ mApp = createQsbApplication()
+ }
+ return mApp!!
+ }
+
+ protected fun createQsbApplication(): QsbApplication {
+ return QsbApplication(this)
+ }
+}
diff --git a/src/com/android/quicksearchbox/QueryTask.java b/src/com/android/quicksearchbox/QueryTask.java
deleted file mode 100644
index 8ea5be9..0000000
--- a/src/com/android/quicksearchbox/QueryTask.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.Consumer;
-import com.android.quicksearchbox.util.Consumers;
-import com.android.quicksearchbox.util.NamedTask;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * A task that gets suggestions from a corpus.
- */
-public class QueryTask<C extends SuggestionCursor> implements NamedTask {
- private static final String TAG = "QSB.QueryTask";
- private static final boolean DBG = false;
-
- private final String mQuery;
- private final int mQueryLimit;
- private final SuggestionCursorProvider<C> mProvider;
- private final Handler mHandler;
- private final Consumer<C> mConsumer;
-
- /**
- * Creates a new query task.
- *
- * @param query Query to run.
- * @param queryLimit The number of suggestions to ask each provider for.
- * @param provider The provider to ask for suggestions.
- * @param handler Handler that {@link Consumer#consume} will
- * get called on. If null, the method is called on the query thread.
- * @param consumer Consumer to notify when the suggestions have been returned.
- */
- public QueryTask(String query, int queryLimit, SuggestionCursorProvider<C> provider,
- Handler handler, Consumer<C> consumer) {
- mQuery = query;
- mQueryLimit = queryLimit;
- mProvider = provider;
- mHandler = handler;
- mConsumer = consumer;
- }
-
- @Override
- public String getName() {
- return mProvider.getName();
- }
-
- @Override
- public void run() {
- final C cursor = mProvider.getSuggestions(mQuery, mQueryLimit);
- if (DBG) Log.d(TAG, "Suggestions from " + mProvider + " = " + cursor);
- Consumers.consumeCloseableAsync(mHandler, mConsumer, cursor);
- }
-
- @Override
- public String toString() {
- return mProvider + "[" + mQuery + "]";
- }
-
- public static <C extends SuggestionCursor> void startQuery(String query,
- int maxResults,
- SuggestionCursorProvider<C> provider,
- NamedTaskExecutor executor, Handler handler,
- Consumer<C> consumer) {
-
- QueryTask<C> task = new QueryTask<C>(query, maxResults, provider, handler,
- consumer);
- executor.execute(task);
- }
-}
diff --git a/src/com/android/quicksearchbox/QueryTask.kt b/src/com/android/quicksearchbox/QueryTask.kt
new file mode 100644
index 0000000..1b5d847
--- /dev/null
+++ b/src/com/android/quicksearchbox/QueryTask.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.os.Handler
+import android.util.Log
+import com.android.quicksearchbox.util.Consumer
+import com.android.quicksearchbox.util.Consumers
+import com.android.quicksearchbox.util.NamedTask
+import com.android.quicksearchbox.util.NamedTaskExecutor
+
+/** A task that gets suggestions from a corpus. */
+class QueryTask<C : SuggestionCursor?>(
+ private val mQuery: String?,
+ private val mQueryLimit: Int,
+ private val mProvider: SuggestionCursorProvider<C>?,
+ handler: Handler?,
+ consumer: Consumer<C>?
+) : NamedTask {
+
+ private val mHandler: Handler?
+
+ private val mConsumer: Consumer<C>?
+
+ @get:Override
+ override val name: String?
+ get() = mProvider?.name
+
+ @Override
+ override fun run() {
+ val cursor = mProvider?.getSuggestions(mQuery, mQueryLimit)
+ if (DBG) Log.d(TAG, "Suggestions from $mProvider = $cursor")
+ Consumers.consumeCloseableAsync(mHandler, mConsumer, cursor)
+ }
+
+ @Override
+ override fun toString(): String {
+ return "$mProvider[$mQuery]"
+ }
+
+ companion object {
+ private const val TAG = "QSB.QueryTask"
+ private const val DBG = false
+
+ @JvmStatic
+ fun <C : SuggestionCursor?> startQuery(
+ query: String?,
+ maxResults: Int,
+ provider: SuggestionCursorProvider<C>?,
+ executor: NamedTaskExecutor,
+ handler: Handler?,
+ consumer: Consumer<C>?
+ ) {
+ val task = QueryTask(query, maxResults, provider, handler, consumer)
+ executor.execute(task)
+ }
+ }
+
+ /**
+ * Creates a new query task.
+ *
+ * @param query Query to run.
+ * @param queryLimit The number of suggestions to ask each provider for.
+ * @param provider The provider to ask for suggestions.
+ * @param handler Handler that [Consumer.consume] will get called on. If null, the method is
+ * called on the query thread.
+ * @param consumer Consumer to notify when the suggestions have been returned.
+ */
+ init {
+ mHandler = handler
+ mConsumer = consumer
+ }
+}
diff --git a/src/com/android/quicksearchbox/ResultFilter.kt b/src/com/android/quicksearchbox/ResultFilter.kt
new file mode 100644
index 0000000..27b03dd
--- /dev/null
+++ b/src/com/android/quicksearchbox/ResultFilter.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** [SuggestionFilter] that accepts only results (not web suggestions). */
+class ResultFilter : SuggestionFilter {
+ override fun accept(s: Suggestion?): Boolean {
+ return !s!!.isWebSearchSuggestion
+ }
+}
diff --git a/src/com/android/quicksearchbox/SearchActivity.java b/src/com/android/quicksearchbox/SearchActivity.java
deleted file mode 100644
index ff21a17..0000000
--- a/src/com/android/quicksearchbox/SearchActivity.java
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Menu;
-import android.view.View;
-
-import com.android.common.Search;
-import com.android.quicksearchbox.ui.SearchActivityView;
-import com.android.quicksearchbox.ui.SuggestionClickListener;
-import com.android.quicksearchbox.ui.SuggestionsAdapter;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.CharMatcher;
-
-import java.io.File;
-
-/**
- * The main activity for Quick Search Box. Shows the search UI.
- *
- */
-public class SearchActivity extends Activity {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SearchActivity";
-
- private static final String SCHEME_CORPUS = "qsb.corpus";
-
- private static final String INTENT_EXTRA_TRACE_START_UP = "trace_start_up";
-
- // Keys for the saved instance state.
- private static final String INSTANCE_KEY_QUERY = "query";
-
- private static final String ACTIVITY_HELP_CONTEXT = "search";
-
- private boolean mTraceStartUp;
- // Measures time from for last onCreate()/onNewIntent() call.
- private LatencyTracker mStartLatencyTracker;
- // Measures time spent inside onCreate()
- private LatencyTracker mOnCreateTracker;
- private int mOnCreateLatency;
- // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume().
- private boolean mStarting;
- // True if the user has taken some action, e.g. launching a search, voice search,
- // or suggestions, since QSB was last started.
- private boolean mTookAction;
-
- private SearchActivityView mSearchActivityView;
-
- private Source mSource;
-
- private Bundle mAppSearchData;
-
- private final Handler mHandler = new Handler();
- private final Runnable mUpdateSuggestionsTask = new Runnable() {
- @Override
- public void run() {
- updateSuggestions();
- }
- };
-
- private final Runnable mShowInputMethodTask = new Runnable() {
- @Override
- public void run() {
- mSearchActivityView.showInputMethodForQuery();
- }
- };
-
- private OnDestroyListener mDestroyListener;
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- mTraceStartUp = getIntent().hasExtra(INTENT_EXTRA_TRACE_START_UP);
- if (mTraceStartUp) {
- String traceFile = new File(getDir("traces", 0), "qsb-start.trace").getAbsolutePath();
- Log.i(TAG, "Writing start-up trace to " + traceFile);
- Debug.startMethodTracing(traceFile);
- }
- recordStartTime();
- if (DBG) Log.d(TAG, "onCreate()");
- super.onCreate(savedInstanceState);
-
- // This forces the HTTP request to check the users domain to be
- // sent as early as possible.
- QsbApplication.get(this).getSearchBaseUrlHelper();
-
- mSource = QsbApplication.get(this).getGoogleSource();
-
- mSearchActivityView = setupContentView();
-
- if (getConfig().showScrollingResults()) {
- mSearchActivityView.setMaxPromotedResults(getConfig().getMaxPromotedResults());
- } else {
- mSearchActivityView.limitResultsToViewHeight();
- }
-
- mSearchActivityView.setSearchClickListener(new SearchActivityView.SearchClickListener() {
- @Override
- public boolean onSearchClicked(int method) {
- return SearchActivity.this.onSearchClicked(method);
- }
- });
-
- mSearchActivityView.setQueryListener(new SearchActivityView.QueryListener() {
- @Override
- public void onQueryChanged() {
- updateSuggestionsBuffered();
- }
- });
-
- mSearchActivityView.setSuggestionClickListener(new ClickHandler());
-
- mSearchActivityView.setVoiceSearchButtonClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onVoiceSearchClicked();
- }
- });
-
- View.OnClickListener finishOnClick = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- };
- mSearchActivityView.setExitClickListener(finishOnClick);
-
- // First get setup from intent
- Intent intent = getIntent();
- setupFromIntent(intent);
- // Then restore any saved instance state
- restoreInstanceState(savedInstanceState);
-
- // Do this at the end, to avoid updating the list view when setSource()
- // is called.
- mSearchActivityView.start();
-
- recordOnCreateDone();
- }
-
- protected SearchActivityView setupContentView() {
- setContentView(R.layout.search_activity);
- return (SearchActivityView) findViewById(R.id.search_activity_view);
- }
-
- protected SearchActivityView getSearchActivityView() {
- return mSearchActivityView;
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- if (DBG) Log.d(TAG, "onNewIntent()");
- recordStartTime();
- setIntent(intent);
- setupFromIntent(intent);
- }
-
- private void recordStartTime() {
- mStartLatencyTracker = new LatencyTracker();
- mOnCreateTracker = new LatencyTracker();
- mStarting = true;
- mTookAction = false;
- }
-
- private void recordOnCreateDone() {
- mOnCreateLatency = mOnCreateTracker.getLatency();
- }
-
- protected void restoreInstanceState(Bundle savedInstanceState) {
- if (savedInstanceState == null) return;
- String query = savedInstanceState.getString(INSTANCE_KEY_QUERY);
- setQuery(query, false);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- // We don't save appSearchData, since we always get the value
- // from the intent and the user can't change it.
-
- outState.putString(INSTANCE_KEY_QUERY, getQuery());
- }
-
- private void setupFromIntent(Intent intent) {
- if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
- String corpusName = getCorpusNameFromUri(intent.getData());
- String query = intent.getStringExtra(SearchManager.QUERY);
- Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
- boolean selectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
-
- setQuery(query, selectAll);
- mAppSearchData = appSearchData;
-
- }
-
- private String getCorpusNameFromUri(Uri uri) {
- if (uri == null) return null;
- if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
- return uri.getAuthority();
- }
-
- private QsbApplication getQsbApplication() {
- return QsbApplication.get(this);
- }
-
- private Config getConfig() {
- return getQsbApplication().getConfig();
- }
-
- protected SearchSettings getSettings() {
- return getQsbApplication().getSettings();
- }
-
- private SuggestionsProvider getSuggestionsProvider() {
- return getQsbApplication().getSuggestionsProvider();
- }
-
- private Logger getLogger() {
- return getQsbApplication().getLogger();
- }
-
- @VisibleForTesting
- public void setOnDestroyListener(OnDestroyListener l) {
- mDestroyListener = l;
- }
-
- @Override
- protected void onDestroy() {
- if (DBG) Log.d(TAG, "onDestroy()");
- mSearchActivityView.destroy();
- super.onDestroy();
- if (mDestroyListener != null) {
- mDestroyListener.onDestroyed();
- }
- }
-
- @Override
- protected void onStop() {
- if (DBG) Log.d(TAG, "onStop()");
- if (!mTookAction) {
- // TODO: This gets logged when starting other activities, e.g. by opening the search
- // settings, or clicking a notification in the status bar.
- // TODO we should log both sets of suggestions in 2-pane mode
- getLogger().logExit(getCurrentSuggestions(), getQuery().length());
- }
- // Close all open suggestion cursors. The query will be redone in onResume()
- // if we come back to this activity.
- mSearchActivityView.clearSuggestions();
- mSearchActivityView.onStop();
- super.onStop();
- }
-
- @Override
- protected void onPause() {
- if (DBG) Log.d(TAG, "onPause()");
- mSearchActivityView.onPause();
- super.onPause();
- }
-
- @Override
- protected void onRestart() {
- if (DBG) Log.d(TAG, "onRestart()");
- super.onRestart();
- }
-
- @Override
- protected void onResume() {
- if (DBG) Log.d(TAG, "onResume()");
- super.onResume();
- updateSuggestionsBuffered();
- mSearchActivityView.onResume();
- if (mTraceStartUp) Debug.stopMethodTracing();
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- // Since the menu items are dynamic, we recreate the menu every time.
- menu.clear();
- createMenuItems(menu, true);
- return true;
- }
-
- public void createMenuItems(Menu menu, boolean showDisabled) {
- getQsbApplication().getHelp().addHelpMenuItem(menu, ACTIVITY_HELP_CONTEXT);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- if (hasFocus) {
- // Launch the IME after a bit
- mHandler.postDelayed(mShowInputMethodTask, 0);
- }
- }
-
- protected String getQuery() {
- return mSearchActivityView.getQuery();
- }
-
- protected void setQuery(String query, boolean selectAll) {
- mSearchActivityView.setQuery(query, selectAll);
- }
-
- /**
- * @return true if a search was performed as a result of this click, false otherwise.
- */
- protected boolean onSearchClicked(int method) {
- String query = CharMatcher.whitespace().trimAndCollapseFrom(getQuery(), ' ');
- if (DBG) Log.d(TAG, "Search clicked, query=" + query);
-
- // Don't do empty queries
- if (TextUtils.getTrimmedLength(query) == 0) return false;
-
- mTookAction = true;
-
- // Log search start
- getLogger().logSearch(method, query.length());
-
- // Start search
- startSearch(mSource, query);
- return true;
- }
-
- protected void startSearch(Source searchSource, String query) {
- Intent intent = searchSource.createSearchIntent(query, mAppSearchData);
- launchIntent(intent);
- }
-
- protected void onVoiceSearchClicked() {
- if (DBG) Log.d(TAG, "Voice Search clicked");
-
- mTookAction = true;
-
- // Log voice search start
- getLogger().logVoiceSearch();
-
- // Start voice search
- Intent intent = mSource.createVoiceSearchIntent(mAppSearchData);
- launchIntent(intent);
- }
-
- protected Source getSearchSource() {
- return mSource;
- }
-
- protected SuggestionCursor getCurrentSuggestions() {
- Suggestions suggestions = mSearchActivityView.getSuggestions();
- if (suggestions == null) {
- return null;
- }
- return suggestions.getResult();
- }
-
- protected SuggestionPosition getCurrentSuggestions(SuggestionsAdapter<?> adapter, long id) {
- SuggestionPosition pos = adapter.getSuggestion(id);
- if (pos == null) {
- return null;
- }
- SuggestionCursor suggestions = pos.getCursor();
- int position = pos.getPosition();
- if (suggestions == null) {
- return null;
- }
- int count = suggestions.getCount();
- if (position < 0 || position >= count) {
- Log.w(TAG, "Invalid suggestion position " + position + ", count = " + count);
- return null;
- }
- suggestions.moveTo(position);
- return pos;
- }
-
- protected void launchIntent(Intent intent) {
- if (DBG) Log.d(TAG, "launchIntent " + intent);
- if (intent == null) {
- return;
- }
- try {
- startActivity(intent);
- } catch (RuntimeException ex) {
- // Since the intents for suggestions specified by suggestion providers,
- // guard against them not being handled, not allowed, etc.
- Log.e(TAG, "Failed to start " + intent.toUri(0), ex);
- }
- }
-
- private boolean launchSuggestion(SuggestionsAdapter<?> adapter, long id) {
- SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
- if (suggestion == null) return false;
-
- if (DBG) Log.d(TAG, "Launching suggestion " + id);
- mTookAction = true;
-
- // Log suggestion click
- getLogger().logSuggestionClick(id, suggestion.getCursor(),
- Logger.SUGGESTION_CLICK_TYPE_LAUNCH);
-
- // Launch intent
- launchSuggestion(suggestion.getCursor(), suggestion.getPosition());
-
- return true;
- }
-
- protected void launchSuggestion(SuggestionCursor suggestions, int position) {
- suggestions.moveTo(position);
- Intent intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData);
- launchIntent(intent);
- }
-
- protected void refineSuggestion(SuggestionsAdapter<?> adapter, long id) {
- if (DBG) Log.d(TAG, "query refine clicked, pos " + id);
- SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
- if (suggestion == null) {
- return;
- }
- String query = suggestion.getSuggestionQuery();
- if (TextUtils.isEmpty(query)) {
- return;
- }
-
- // Log refine click
- getLogger().logSuggestionClick(id, suggestion.getCursor(),
- Logger.SUGGESTION_CLICK_TYPE_REFINE);
-
- // Put query + space in query text view
- String queryWithSpace = query + ' ';
- setQuery(queryWithSpace, false);
- updateSuggestions();
- mSearchActivityView.focusQueryTextView();
- }
-
- private void updateSuggestionsBuffered() {
- if (DBG) Log.d(TAG, "updateSuggestionsBuffered()");
- mHandler.removeCallbacks(mUpdateSuggestionsTask);
- long delay = getConfig().getTypingUpdateSuggestionsDelayMillis();
- mHandler.postDelayed(mUpdateSuggestionsTask, delay);
- }
-
- private void gotSuggestions(Suggestions suggestions) {
- if (mStarting) {
- mStarting = false;
- String source = getIntent().getStringExtra(Search.SOURCE);
- int latency = mStartLatencyTracker.getLatency();
- getLogger().logStart(mOnCreateLatency, latency, source);
- getQsbApplication().onStartupComplete();
- }
- }
-
- public void updateSuggestions() {
- if (DBG) Log.d(TAG, "updateSuggestions()");
- final String query = CharMatcher.whitespace().trimLeadingFrom(getQuery());
- updateSuggestions(query, mSource);
- }
-
- protected void updateSuggestions(String query, Source source) {
- if (DBG) Log.d(TAG, "updateSuggestions(\"" + query+"\"," + source + ")");
- Suggestions suggestions = getSuggestionsProvider().getSuggestions(
- query, source);
-
- // Log start latency if this is the first suggestions update
- gotSuggestions(suggestions);
-
- showSuggestions(suggestions);
- }
-
- protected void showSuggestions(Suggestions suggestions) {
- mSearchActivityView.setSuggestions(suggestions);
- }
-
- private class ClickHandler implements SuggestionClickListener {
-
- @Override
- public void onSuggestionClicked(SuggestionsAdapter<?> adapter, long id) {
- launchSuggestion(adapter, id);
- }
-
- @Override
- public void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long id) {
- refineSuggestion(adapter, id);
- }
- }
-
- public interface OnDestroyListener {
- void onDestroyed();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SearchActivity.kt b/src/com/android/quicksearchbox/SearchActivity.kt
new file mode 100644
index 0000000..0620b97
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchActivity.kt
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox
+
+import android.app.Activity
+import android.app.SearchManager
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Debug
+import android.os.Handler
+import android.os.Looper
+import android.text.TextUtils
+import android.util.Log
+import android.view.Menu
+import android.view.View
+import com.android.common.Search
+import com.android.quicksearchbox.ui.SearchActivityView
+import com.android.quicksearchbox.ui.SuggestionClickListener
+import com.android.quicksearchbox.ui.SuggestionsAdapter
+import com.google.common.annotations.VisibleForTesting
+import com.google.common.base.CharMatcher
+import java.io.File
+
+/** The main activity for Quick Search Box. Shows the search UI. */
+class SearchActivity : Activity() {
+ private var mTraceStartUp = false
+
+ // Measures time from for last onCreate()/onNewIntent() call.
+ private var mStartLatencyTracker: LatencyTracker? = null
+
+ // Measures time spent inside onCreate()
+ private var mOnCreateTracker: LatencyTracker? = null
+ private var mOnCreateLatency = 0
+
+ // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume().
+ private var mStarting = false
+
+ // True if the user has taken some action, e.g. launching a search, voice search,
+ // or suggestions, since QSB was last started.
+ private var mTookAction = false
+ private var mSearchActivityView: SearchActivityView? = null
+ protected var searchSource: Source? = null
+ private set
+ private var mAppSearchData: Bundle? = null
+ private val mHandler: Handler = Handler(Looper.getMainLooper())
+ private val mUpdateSuggestionsTask: Runnable =
+ object : Runnable {
+ @Override
+ override fun run() {
+ updateSuggestions()
+ }
+ }
+ private val mShowInputMethodTask: Runnable =
+ object : Runnable {
+ @Override
+ override fun run() {
+ mSearchActivityView?.showInputMethodForQuery()
+ }
+ }
+ private var mDestroyListener: OnDestroyListener? = null
+
+ /** Called when the activity is first created. */
+ @Override
+ override fun onCreate(savedInstanceState: Bundle?) {
+ mTraceStartUp = getIntent().hasExtra(INTENT_EXTRA_TRACE_START_UP)
+ if (mTraceStartUp) {
+ val traceFile: String = File(getDir("traces", 0), "qsb-start.trace").getAbsolutePath()
+ Log.i(TAG, "Writing start-up trace to $traceFile")
+ Debug.startMethodTracing(traceFile)
+ }
+ recordStartTime()
+ if (DBG) Log.d(TAG, "onCreate()")
+ super.onCreate(savedInstanceState)
+
+ // This forces the HTTP request to check the users domain to be
+ // sent as early as possible.
+ QsbApplication[this].searchBaseUrlHelper
+ searchSource = QsbApplication[this].googleSource
+ mSearchActivityView = setupContentView()
+ if (config?.showScrollingResults() == true) {
+ mSearchActivityView?.setMaxPromotedResults(config!!.maxPromotedResults)
+ } else {
+ mSearchActivityView?.limitResultsToViewHeight()
+ }
+ mSearchActivityView?.setSearchClickListener(
+ object : SearchActivityView.SearchClickListener {
+ @Override
+ override fun onSearchClicked(method: Int): Boolean {
+ return this@SearchActivity.onSearchClicked(method)
+ }
+ }
+ )
+ mSearchActivityView?.setQueryListener(
+ object : SearchActivityView.QueryListener {
+ @Override
+ override fun onQueryChanged() {
+ updateSuggestionsBuffered()
+ }
+ }
+ )
+ mSearchActivityView?.setSuggestionClickListener(ClickHandler())
+ mSearchActivityView?.setVoiceSearchButtonClickListener(
+ object : View.OnClickListener {
+ @Override
+ override fun onClick(view: View?) {
+ onVoiceSearchClicked()
+ }
+ }
+ )
+ val finishOnClick: View.OnClickListener =
+ object : View.OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ finish()
+ }
+ }
+ mSearchActivityView?.setExitClickListener(finishOnClick)
+
+ // First get setup from intent
+ val intent: Intent = getIntent()
+ setupFromIntent(intent)
+ // Then restore any saved instance state
+ restoreInstanceState(savedInstanceState)
+
+ // Do this at the end, to avoid updating the list view when setSource()
+ // is called.
+ mSearchActivityView?.start()
+ recordOnCreateDone()
+ }
+
+ protected fun setupContentView(): SearchActivityView {
+ setContentView(R.layout.search_activity)
+ return findViewById(R.id.search_activity_view) as SearchActivityView
+ }
+
+ protected val searchActivityView: SearchActivityView?
+ get() = mSearchActivityView
+
+ @Override
+ protected override fun onNewIntent(intent: Intent) {
+ if (DBG) Log.d(TAG, "onNewIntent()")
+ recordStartTime()
+ setIntent(intent)
+ setupFromIntent(intent)
+ }
+
+ private fun recordStartTime() {
+ mStartLatencyTracker = LatencyTracker()
+ mOnCreateTracker = LatencyTracker()
+ mStarting = true
+ mTookAction = false
+ }
+
+ private fun recordOnCreateDone() {
+ mOnCreateLatency = mOnCreateTracker!!.latency
+ }
+
+ protected fun restoreInstanceState(savedInstanceState: Bundle?) {
+ if (savedInstanceState == null) return
+ val query: String? = savedInstanceState.getString(INSTANCE_KEY_QUERY)
+ setQuery(query, false)
+ }
+
+ @Override
+ protected override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ // We don't save appSearchData, since we always get the value
+ // from the intent and the user can't change it.
+ outState.putString(INSTANCE_KEY_QUERY, query)
+ }
+
+ private fun setupFromIntent(intent: Intent) {
+ if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0).toString() + ")")
+ @Suppress("UNUSED_VARIABLE") val corpusName = getCorpusNameFromUri(intent.getData())
+ val query: String? = intent.getStringExtra(SearchManager.QUERY)
+ val appSearchData: Bundle? = intent.getBundleExtra(SearchManager.APP_DATA)
+ val selectAll: Boolean = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false)
+ setQuery(query, selectAll)
+ mAppSearchData = appSearchData
+ }
+
+ private fun getCorpusNameFromUri(uri: Uri?): String? {
+ if (uri == null) return null
+ return if (SCHEME_CORPUS != uri.getScheme()) null else uri.getAuthority()
+ }
+
+ private val qsbApplication: QsbApplication
+ get() = QsbApplication[this]
+
+ private val config: Config?
+ get() = qsbApplication.config
+
+ protected val settings: SearchSettings?
+ get() = qsbApplication.settings
+
+ private val suggestionsProvider: SuggestionsProvider?
+ get() = qsbApplication.suggestionsProvider
+
+ private val logger: Logger?
+ get() = qsbApplication.logger
+
+ @VisibleForTesting
+ fun setOnDestroyListener(l: OnDestroyListener?) {
+ mDestroyListener = l
+ }
+
+ @Override
+ protected override fun onDestroy() {
+ if (DBG) Log.d(TAG, "onDestroy()")
+ mSearchActivityView?.destroy()
+ super.onDestroy()
+ if (mDestroyListener != null) {
+ mDestroyListener?.onDestroyed()
+ }
+ }
+
+ @Override
+ protected override fun onStop() {
+ if (DBG) Log.d(TAG, "onStop()")
+ if (!mTookAction) {
+ // TODO: This gets logged when starting other activities, e.g. by opening the search
+ // settings, or clicking a notification in the status bar.
+ // TODO we should log both sets of suggestions in 2-pane mode
+ logger?.logExit(currentSuggestions, query!!.length)
+ }
+ // Close all open suggestion cursors. The query will be redone in onResume()
+ // if we come back to this activity.
+ mSearchActivityView?.clearSuggestions()
+ mSearchActivityView?.onStop()
+ super.onStop()
+ }
+
+ @Override
+ protected override fun onPause() {
+ if (DBG) Log.d(TAG, "onPause()")
+ mSearchActivityView?.onPause()
+ super.onPause()
+ }
+
+ @Override
+ protected override fun onRestart() {
+ if (DBG) Log.d(TAG, "onRestart()")
+ super.onRestart()
+ }
+
+ @Override
+ protected override fun onResume() {
+ if (DBG) Log.d(TAG, "onResume()")
+ super.onResume()
+ updateSuggestionsBuffered()
+ mSearchActivityView?.onResume()
+ if (mTraceStartUp) Debug.stopMethodTracing()
+ }
+
+ @Override
+ override fun onPrepareOptionsMenu(menu: Menu): Boolean {
+ // Since the menu items are dynamic, we recreate the menu every time.
+ menu.clear()
+ createMenuItems(menu, true)
+ return true
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun createMenuItems(menu: Menu, showDisabled: Boolean) {
+ qsbApplication.help.addHelpMenuItem(menu, ACTIVITY_HELP_CONTEXT)
+ }
+
+ @Override
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ if (hasFocus) {
+ // Launch the IME after a bit
+ mHandler.postDelayed(mShowInputMethodTask, 0)
+ }
+ }
+
+ protected val query: String?
+ get() = mSearchActivityView?.query
+
+ protected fun setQuery(query: String?, selectAll: Boolean) {
+ mSearchActivityView?.setQuery(query, selectAll)
+ }
+
+ /** @return true if a search was performed as a result of this click, false otherwise. */
+ protected fun onSearchClicked(method: Int): Boolean {
+ val query: String = CharMatcher.whitespace().trimAndCollapseFrom(query as CharSequence, ' ')
+ if (DBG) Log.d(TAG, "Search clicked, query=$query")
+
+ // Don't do empty queries
+ if (TextUtils.getTrimmedLength(query) == 0) return false
+ mTookAction = true
+
+ // Log search start
+ logger?.logSearch(method, query.length)
+
+ // Start search
+ startSearch(searchSource, query)
+ return true
+ }
+
+ protected fun startSearch(searchSource: Source?, query: String?) {
+ val intent: Intent? = searchSource!!.createSearchIntent(query, mAppSearchData)
+ launchIntent(intent)
+ }
+
+ protected fun onVoiceSearchClicked() {
+ if (DBG) Log.d(TAG, "Voice Search clicked")
+ mTookAction = true
+
+ // Log voice search start
+ logger?.logVoiceSearch()
+
+ // Start voice search
+ val intent: Intent? = searchSource!!.createVoiceSearchIntent(mAppSearchData)
+ launchIntent(intent)
+ }
+
+ protected val currentSuggestions: SuggestionCursor?
+ get() {
+ val suggestions: Suggestions = mSearchActivityView?.suggestions ?: return null
+ return suggestions.getResult()
+ }
+
+ protected fun getCurrentSuggestions(
+ adapter: SuggestionsAdapter<*>?,
+ id: Long
+ ): SuggestionPosition? {
+ val pos: SuggestionPosition = adapter?.getSuggestion(id) ?: return null
+ val suggestions: SuggestionCursor? = pos.cursor
+ val position: Int = pos.position
+ if (suggestions == null) {
+ return null
+ }
+ val count: Int = suggestions.count
+ if (position < 0 || position >= count) {
+ Log.w(TAG, "Invalid suggestion position $position, count = $count")
+ return null
+ }
+ suggestions.moveTo(position)
+ return pos
+ }
+
+ protected fun launchIntent(intent: Intent?) {
+ if (DBG) Log.d(TAG, "launchIntent $intent")
+ if (intent == null) {
+ return
+ }
+ try {
+ startActivity(intent)
+ } catch (ex: RuntimeException) {
+ // Since the intents for suggestions specified by suggestion providers,
+ // guard against them not being handled, not allowed, etc.
+ Log.e(TAG, "Failed to start " + intent.toUri(0), ex)
+ }
+ }
+
+ private fun launchSuggestion(adapter: SuggestionsAdapter<*>?, id: Long): Boolean {
+ val suggestion = getCurrentSuggestions(adapter, id) ?: return false
+ if (DBG) Log.d(TAG, "Launching suggestion $id")
+ mTookAction = true
+
+ // Log suggestion click
+ logger?.logSuggestionClick(id, suggestion.cursor, Logger.SUGGESTION_CLICK_TYPE_LAUNCH)
+
+ // Launch intent
+ launchSuggestion(suggestion.cursor, suggestion.position)
+ return true
+ }
+
+ protected fun launchSuggestion(suggestions: SuggestionCursor?, position: Int) {
+ suggestions?.moveTo(position)
+ val intent: Intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData)
+ launchIntent(intent)
+ }
+
+ protected fun refineSuggestion(adapter: SuggestionsAdapter<*>?, id: Long) {
+ if (DBG) Log.d(TAG, "query refine clicked, pos $id")
+ val suggestion = getCurrentSuggestions(adapter, id) ?: return
+ val query: String? = suggestion.suggestionQuery
+ if (TextUtils.isEmpty(query)) {
+ return
+ }
+
+ // Log refine click
+ logger?.logSuggestionClick(id, suggestion.cursor, Logger.SUGGESTION_CLICK_TYPE_REFINE)
+
+ // Put query + space in query text view
+ val queryWithSpace = "$query "
+ setQuery(queryWithSpace, false)
+ updateSuggestions()
+ mSearchActivityView?.focusQueryTextView()
+ }
+
+ private fun updateSuggestionsBuffered() {
+ if (DBG) Log.d(TAG, "updateSuggestionsBuffered()")
+ mHandler.removeCallbacks(mUpdateSuggestionsTask)
+ val delay: Long = config!!.typingUpdateSuggestionsDelayMillis
+ mHandler.postDelayed(mUpdateSuggestionsTask, delay)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun gotSuggestions(suggestions: Suggestions?) {
+ if (mStarting) {
+ mStarting = false
+ val source: String? = getIntent().getStringExtra(Search.SOURCE)
+ val latency: Int = mStartLatencyTracker!!.latency
+ logger?.logStart(mOnCreateLatency, latency, source)
+ qsbApplication.onStartupComplete()
+ }
+ }
+
+ fun updateSuggestions() {
+ if (DBG) Log.d(TAG, "updateSuggestions()")
+ val query: String = CharMatcher.whitespace().trimLeadingFrom(query as CharSequence)
+ updateSuggestions(query, searchSource)
+ }
+
+ protected fun updateSuggestions(query: String, source: Source?) {
+ if (DBG) Log.d(TAG, "updateSuggestions(\"$query\",$source)")
+ val suggestions = suggestionsProvider?.getSuggestions(query, source!!)
+
+ // Log start latency if this is the first suggestions update
+ gotSuggestions(suggestions)
+ showSuggestions(suggestions)
+ }
+
+ protected fun showSuggestions(suggestions: Suggestions?) {
+ mSearchActivityView?.suggestions = suggestions
+ }
+
+ private inner class ClickHandler : SuggestionClickListener {
+ @Override
+ override fun onSuggestionClicked(adapter: SuggestionsAdapter<*>?, suggestionId: Long) {
+ launchSuggestion(adapter, suggestionId)
+ }
+
+ @Override
+ override fun onSuggestionQueryRefineClicked(
+ adapter: SuggestionsAdapter<*>?,
+ suggestionId: Long
+ ) {
+ refineSuggestion(adapter, suggestionId)
+ }
+ }
+
+ interface OnDestroyListener {
+ fun onDestroyed()
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SearchActivity"
+ private const val SCHEME_CORPUS = "qsb.corpus"
+ private const val INTENT_EXTRA_TRACE_START_UP = "trace_start_up"
+
+ // Keys for the saved instance state.
+ private const val INSTANCE_KEY_QUERY = "query"
+ private const val ACTIVITY_HELP_CONTEXT = "search"
+ }
+}
diff --git a/src/com/android/quicksearchbox/SearchSettings.java b/src/com/android/quicksearchbox/SearchSettings.java
deleted file mode 100644
index 7b1a8a9..0000000
--- a/src/com/android/quicksearchbox/SearchSettings.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-
-/**
- * Interface for search settings.
- *
- * NOTE: Currently, this is not used very widely, in most instances
- * implementers of this interface are passed around by class name.
- * Should this be deprecated ?
- */
-public interface SearchSettings {
-
- public void upgradeSettingsIfNeeded();
-
- /**
- * Informs our listeners about the updated settings data.
- */
- public void broadcastSettingsChanged();
-
- public int getNextVoiceSearchHintIndex(int size);
-
- public void resetVoiceSearchHintFirstSeenTime();
-
- public boolean haveVoiceSearchHintsExpired(int currentVoiceSearchVersion);
-
- /**
- * Determines whether google.com should be used as the base path
- * for all searches (as opposed to using its country specific variants).
- */
- public boolean shouldUseGoogleCom();
-
- public void setUseGoogleCom(boolean useGoogleCom);
-
- public long getSearchBaseDomainApplyTime();
-
- public String getSearchBaseDomain();
-
- public void setSearchBaseDomain(String searchBaseUrl);
-}
diff --git a/src/com/android/quicksearchbox/SearchSettings.kt b/src/com/android/quicksearchbox/SearchSettings.kt
new file mode 100644
index 0000000..b3d2755
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchSettings.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/**
+ * Interface for search settings.
+ *
+ * NOTE: Currently, this is not used very widely, in most instances implementers of this interface
+ * are passed around by class name. Should this be deprecated ?
+ */
+interface SearchSettings {
+ fun upgradeSettingsIfNeeded()
+
+ /** Informs our listeners about the updated settings data. */
+ fun broadcastSettingsChanged()
+ fun getNextVoiceSearchHintIndex(size: Int): Int
+ fun resetVoiceSearchHintFirstSeenTime()
+ fun haveVoiceSearchHintsExpired(currentVoiceSearchVersion: Int): Boolean
+
+ /**
+ * Determines whether google.com should be used as the base path for all searches (as opposed to
+ * using its country specific variants).
+ */
+ fun shouldUseGoogleCom(): Boolean
+ fun setUseGoogleCom(useGoogleCom: Boolean)
+ val searchBaseDomainApplyTime: Long
+ var searchBaseDomain: String?
+}
diff --git a/src/com/android/quicksearchbox/SearchSettingsImpl.java b/src/com/android/quicksearchbox/SearchSettingsImpl.java
deleted file mode 100644
index 1fc74ea..0000000
--- a/src/com/android/quicksearchbox/SearchSettingsImpl.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.app.SearchManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.util.Log;
-
-import com.android.common.SharedPreferencesCompat;
-
-/**
- * Manages user settings.
- */
-public class SearchSettingsImpl implements SearchSettings {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SearchSettingsImpl";
-
- // Name of the preferences file used to store search preference
- public static final String PREFERENCES_NAME = "SearchSettings";
-
- /**
- * Preference key used for storing the index of the next voice search hint to show.
- */
- private static final String NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint";
-
- /**
- * Preference key used to store the time at which the first voice search hint was displayed.
- */
- private static final String FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time";
-
- /**
- * Preference key for the version of voice search we last got hints from.
- */
- private static final String LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version";
-
- /**
- * Preference key for storing whether searches always go to google.com. Public
- * so that it can be used by PreferenceControllers.
- */
- public static final String USE_GOOGLE_COM_PREF = "use_google_com";
-
- /**
- * Preference key for the base search URL. This value is normally set by
- * a SearchBaseUrlHelper instance. Public so classes can listen to changes
- * on this key.
- */
- public static final String SEARCH_BASE_DOMAIN_PREF = "search_base_domain";
-
- /**
- * This is the time at which the base URL was stored, and is set using
- * @link{System.currentTimeMillis()}.
- */
- private static final String SEARCH_BASE_DOMAIN_APPLY_TIME = "search_base_domain_apply_time";
-
- private final Context mContext;
-
- private final Config mConfig;
-
- public SearchSettingsImpl(Context context, Config config) {
- mContext = context;
- mConfig = config;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected Config getConfig() {
- return mConfig;
- }
-
- @Override
- public void upgradeSettingsIfNeeded() {
- }
-
- public SharedPreferences getSearchPreferences() {
- return getContext().getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
- }
-
- protected void storeBoolean(String name, boolean value) {
- SharedPreferencesCompat.apply(getSearchPreferences().edit().putBoolean(name, value));
- }
-
- protected void storeInt(String name, int value) {
- SharedPreferencesCompat.apply(getSearchPreferences().edit().putInt(name, value));
- }
-
- protected void storeLong(String name, long value) {
- SharedPreferencesCompat.apply(getSearchPreferences().edit().putLong(name, value));
- }
-
- protected void storeString(String name, String value) {
- SharedPreferencesCompat.apply(getSearchPreferences().edit().putString(name, value));
- }
-
- protected void removePref(String name) {
- SharedPreferencesCompat.apply(getSearchPreferences().edit().remove(name));
- }
-
- /**
- * Informs our listeners about the updated settings data.
- */
- @Override
- public void broadcastSettingsChanged() {
- // We use a message broadcast since the listeners could be in multiple processes.
- Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED);
- Log.i(TAG, "Broadcasting: " + intent);
- getContext().sendBroadcast(intent);
- }
-
- @Override
- public int getNextVoiceSearchHintIndex(int size) {
- int i = getAndIncrementIntPreference(getSearchPreferences(),
- NEXT_VOICE_SEARCH_HINT_INDEX_PREF);
- return i % size;
- }
-
- // TODO: Could this be made atomic to avoid races?
- private int getAndIncrementIntPreference(SharedPreferences prefs, String name) {
- int i = prefs.getInt(name, 0);
- storeInt(name, i + 1);
- return i;
- }
-
- @Override
- public void resetVoiceSearchHintFirstSeenTime() {
- storeLong(FIRST_VOICE_HINT_DISPLAY_TIME, System.currentTimeMillis());
- }
-
- @Override
- public boolean haveVoiceSearchHintsExpired(int currentVoiceSearchVersion) {
- SharedPreferences prefs = getSearchPreferences();
-
- if (currentVoiceSearchVersion != 0) {
- long currentTime = System.currentTimeMillis();
- int lastVoiceSearchVersion = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0);
- long firstHintTime = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0);
- if (firstHintTime == 0 || currentVoiceSearchVersion != lastVoiceSearchVersion) {
- SharedPreferencesCompat.apply(prefs.edit()
- .putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion)
- .putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime));
- firstHintTime = currentTime;
- }
- if (currentTime - firstHintTime > getConfig().getVoiceSearchHintActivePeriod()) {
- if (DBG) Log.d(TAG, "Voice seach hint period expired; not showing hints.");
- return true;
- } else {
- return false;
- }
- } else {
- if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints.");
- return true;
- }
- }
-
- /**
- * @return true if user searches should always be based at google.com, false
- * otherwise.
- */
- @Override
- public boolean shouldUseGoogleCom() {
- // Note that this preserves the old behaviour of using google.com
- // for searches, with the gl= parameter set.
- return getSearchPreferences().getBoolean(USE_GOOGLE_COM_PREF, true);
- }
-
- @Override
- public void setUseGoogleCom(boolean useGoogleCom) {
- storeBoolean(USE_GOOGLE_COM_PREF, useGoogleCom);
- }
-
- @Override
- public long getSearchBaseDomainApplyTime() {
- return getSearchPreferences().getLong(SEARCH_BASE_DOMAIN_APPLY_TIME, -1);
- }
-
- @Override
- public String getSearchBaseDomain() {
- // Note that the only time this will return null is on the first run
- // of the app, or when settings have been cleared. Callers should
- // ideally check that getSearchBaseDomainApplyTime() is not -1 before
- // calling this function.
- return getSearchPreferences().getString(SEARCH_BASE_DOMAIN_PREF, null);
- }
-
- @Override
- public void setSearchBaseDomain(String searchBaseUrl) {
- Editor sharedPrefEditor = getSearchPreferences().edit();
- sharedPrefEditor.putString(SEARCH_BASE_DOMAIN_PREF, searchBaseUrl);
- sharedPrefEditor.putLong(SEARCH_BASE_DOMAIN_APPLY_TIME, System.currentTimeMillis());
-
- SharedPreferencesCompat.apply(sharedPrefEditor);
- }
-}
diff --git a/src/com/android/quicksearchbox/SearchSettingsImpl.kt b/src/com/android/quicksearchbox/SearchSettingsImpl.kt
new file mode 100644
index 0000000..8168f78
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchSettingsImpl.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.Editor
+import android.util.Log
+import com.android.common.SharedPreferencesCompat
+
+/** Manages user settings. */
+class SearchSettingsImpl(context: Context?, config: Config?) : SearchSettings {
+ private val mContext: Context?
+ protected val config: Config?
+ protected val context: Context?
+ get() = mContext
+
+ @Override override fun upgradeSettingsIfNeeded() {}
+
+ val searchPreferences: SharedPreferences
+ get() = context!!.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ protected fun storeBoolean(name: String?, value: Boolean) {
+ SharedPreferencesCompat.apply(searchPreferences.edit().putBoolean(name, value))
+ }
+
+ protected fun storeInt(name: String?, value: Int) {
+ SharedPreferencesCompat.apply(searchPreferences.edit().putInt(name, value))
+ }
+
+ protected fun storeLong(name: String?, value: Long) {
+ SharedPreferencesCompat.apply(searchPreferences.edit().putLong(name, value))
+ }
+
+ protected fun storeString(name: String?, value: String?) {
+ SharedPreferencesCompat.apply(searchPreferences.edit().putString(name, value))
+ }
+
+ protected fun removePref(name: String?) {
+ SharedPreferencesCompat.apply(searchPreferences.edit().remove(name))
+ }
+
+ /** Informs our listeners about the updated settings data. */
+ @Override
+ override fun broadcastSettingsChanged() {
+ // We use a message broadcast since the listeners could be in multiple processes.
+ val intent = Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED)
+ Log.i(TAG, "Broadcasting: $intent")
+ context?.sendBroadcast(intent)
+ }
+
+ @Override
+ override fun getNextVoiceSearchHintIndex(size: Int): Int {
+ val i = getAndIncrementIntPreference(searchPreferences, NEXT_VOICE_SEARCH_HINT_INDEX_PREF)
+ return i % size
+ }
+
+ // TODO: Could this be made atomic to avoid races?
+ private fun getAndIncrementIntPreference(prefs: SharedPreferences, name: String): Int {
+ val i: Int = prefs.getInt(name, 0)
+ storeInt(name, i + 1)
+ return i
+ }
+
+ @Override
+ override fun resetVoiceSearchHintFirstSeenTime() {
+ storeLong(FIRST_VOICE_HINT_DISPLAY_TIME, System.currentTimeMillis())
+ }
+
+ @Override
+ override fun haveVoiceSearchHintsExpired(currentVoiceSearchVersion: Int): Boolean {
+ val prefs: SharedPreferences = searchPreferences
+ return if (currentVoiceSearchVersion != 0) {
+ val currentTime: Long = System.currentTimeMillis()
+ val lastVoiceSearchVersion: Int = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0)
+ var firstHintTime: Long = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0)
+ if (firstHintTime == 0L || currentVoiceSearchVersion != lastVoiceSearchVersion) {
+ SharedPreferencesCompat.apply(
+ prefs
+ .edit()
+ .putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion)
+ .putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime)
+ )
+ firstHintTime = currentTime
+ }
+ if (currentTime - firstHintTime > config!!.voiceSearchHintActivePeriod) {
+ if (DBG) Log.d(TAG, "Voice search hint period expired; not showing hints.")
+ return true
+ } else {
+ false
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints.")
+ true
+ }
+ }
+
+ /** @return true if user searches should always be based at google.com, false otherwise. */
+ @Override
+ override fun shouldUseGoogleCom(): Boolean {
+ // Note that this preserves the old behaviour of using google.com
+ // for searches, with the gl= parameter set.
+ return searchPreferences.getBoolean(USE_GOOGLE_COM_PREF, true)
+ }
+
+ @Override
+ override fun setUseGoogleCom(useGoogleCom: Boolean) {
+ storeBoolean(USE_GOOGLE_COM_PREF, useGoogleCom)
+ }
+
+ @get:Override
+ override val searchBaseDomainApplyTime: Long
+ get() = searchPreferences.getLong(SEARCH_BASE_DOMAIN_APPLY_TIME, -1)
+
+ // Note that the only time this will return null is on the first run
+ // of the app, or when settings have been cleared. Callers should
+ // ideally check that getSearchBaseDomainApplyTime() is not -1 before
+ // calling this function.
+ @get:Override
+ @set:Override
+ override var searchBaseDomain: String?
+ get() = searchPreferences.getString(SEARCH_BASE_DOMAIN_PREF, null)
+ set(searchBaseUrl) {
+ val sharedPrefEditor: Editor = searchPreferences.edit()
+ sharedPrefEditor.putString(SEARCH_BASE_DOMAIN_PREF, searchBaseUrl)
+ sharedPrefEditor.putLong(SEARCH_BASE_DOMAIN_APPLY_TIME, System.currentTimeMillis())
+ SharedPreferencesCompat.apply(sharedPrefEditor)
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SearchSettingsImpl"
+
+ // Name of the preferences file used to store search preference
+ const val PREFERENCES_NAME = "SearchSettings"
+
+ /** Preference key used for storing the index of the next voice search hint to show. */
+ private const val NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint"
+
+ /** Preference key used to store the time at which the first voice search hint was displayed. */
+ private const val FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time"
+
+ /** Preference key for the version of voice search we last got hints from. */
+ private const val LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version"
+
+ /**
+ * Preference key for storing whether searches always go to google.com. Public so that it can be
+ * used by PreferenceControllers.
+ */
+ const val USE_GOOGLE_COM_PREF = "use_google_com"
+
+ /**
+ * Preference key for the base search URL. This value is normally set by a SearchBaseUrlHelper
+ * instance. Public so classes can listen to changes on this key.
+ */
+ const val SEARCH_BASE_DOMAIN_PREF = "search_base_domain"
+
+ /**
+ * This is the time at which the base URL was stored, and is set using
+ * @link{System.currentTimeMillis()}.
+ */
+ private const val SEARCH_BASE_DOMAIN_APPLY_TIME = "search_base_domain_apply_time"
+ }
+
+ init {
+ mContext = context
+ this.config = config
+ }
+}
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.java b/src/com/android/quicksearchbox/SearchWidgetProvider.java
deleted file mode 100644
index fdd8cf3..0000000
--- a/src/com/android/quicksearchbox/SearchWidgetProvider.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.common.Search;
-import com.android.common.speech.Recognition;
-import com.android.quicksearchbox.util.Util;
-
-import android.app.Activity;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.SearchManager;
-import android.appwidget.AppWidgetManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.speech.RecognizerIntent;
-import android.text.Annotation;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import java.util.ArrayList;
-import java.util.Random;
-
-/**
- * Search widget provider.
- *
- */
-public class SearchWidgetProvider extends BroadcastReceiver {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SearchWidgetProvider";
-
- /**
- * The {@link Search#SOURCE} value used when starting searches from the search widget.
- */
- private static final String WIDGET_SEARCH_SOURCE = "launcher-widget";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0) + ")");
- String action = intent.getAction();
- if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
- // nothing needs doing
- } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
- updateSearchWidgets(context);
- } else {
- if (DBG) Log.d(TAG, "Unhandled intent action=" + action);
- }
- }
-
- private static SearchWidgetState[] getSearchWidgetStates(Context context) {
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- int[] appWidgetIds = appWidgetManager.getAppWidgetIds(myComponentName(context));
- SearchWidgetState[] states = new SearchWidgetState[appWidgetIds.length];
- for (int i = 0; i<appWidgetIds.length; ++i) {
- states[i] = getSearchWidgetState(context, appWidgetIds[i]);
- }
- return states;
- }
-
-
- /**
- * Updates all search widgets.
- */
- public static void updateSearchWidgets(Context context) {
- if (DBG) Log.d(TAG, "updateSearchWidgets");
- SearchWidgetState[] states = getSearchWidgetStates(context);
-
- for (SearchWidgetState state : states) {
- state.updateWidget(context, AppWidgetManager.getInstance(context));
- }
- }
-
- /**
- * Gets the component name of this search widget provider.
- */
- private static ComponentName myComponentName(Context context) {
- String pkg = context.getPackageName();
- String cls = pkg + ".SearchWidgetProvider";
- return new ComponentName(pkg, cls);
- }
-
- private static Intent createQsbActivityIntent(Context context, String action,
- Bundle widgetAppData) {
- Intent qsbIntent = new Intent(action);
- qsbIntent.setPackage(context.getPackageName());
- qsbIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData);
- return qsbIntent;
- }
-
- private static SearchWidgetState getSearchWidgetState(Context context, int appWidgetId) {
- if (DBG) Log.d(TAG, "Creating appwidget state " + appWidgetId);
- SearchWidgetState state = new SearchWidgetState(appWidgetId);
-
- Bundle widgetAppData = new Bundle();
- widgetAppData.putString(Search.SOURCE, WIDGET_SEARCH_SOURCE);
-
- // Text field click
- Intent qsbIntent = createQsbActivityIntent(
- context,
- SearchManager.INTENT_ACTION_GLOBAL_SEARCH,
- widgetAppData);
- state.setQueryTextViewIntent(qsbIntent);
-
- // Voice search button
- Intent voiceSearchIntent = getVoiceSearchIntent(context, widgetAppData);
- state.setVoiceSearchIntent(voiceSearchIntent);
-
- return state;
- }
-
- private static Intent getVoiceSearchIntent(Context context, Bundle widgetAppData) {
- VoiceSearch voiceSearch = QsbApplication.get(context).getVoiceSearch();
- return voiceSearch.createVoiceWebSearchIntent(widgetAppData);
- }
-
- private static class SearchWidgetState {
- private final int mAppWidgetId;
- private Intent mQueryTextViewIntent;
- private Intent mVoiceSearchIntent;
-
- public SearchWidgetState(int appWidgetId) {
- mAppWidgetId = appWidgetId;
- }
-
- public void setQueryTextViewIntent(Intent queryTextViewIntent) {
- mQueryTextViewIntent = queryTextViewIntent;
- }
-
- public void setVoiceSearchIntent(Intent voiceSearchIntent) {
- mVoiceSearchIntent = voiceSearchIntent;
- }
-
- public void updateWidget(Context context,AppWidgetManager appWidgetMgr) {
- if (DBG) Log.d(TAG, "Updating appwidget " + mAppWidgetId);
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
-
- setOnClickActivityIntent(context, views, R.id.search_widget_text,
- mQueryTextViewIntent);
- // Voice Search button
- if (mVoiceSearchIntent != null) {
- setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn,
- mVoiceSearchIntent);
- views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE);
- } else {
- views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
- }
-
- appWidgetMgr.updateAppWidget(mAppWidgetId, views);
- }
-
- private void setOnClickActivityIntent(Context context, RemoteViews views, int viewId,
- Intent intent) {
- intent.setPackage(context.getPackageName());
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
- views.setOnClickPendingIntent(viewId, pendingIntent);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.kt b/src/com/android/quicksearchbox/SearchWidgetProvider.kt
new file mode 100644
index 0000000..54588ec
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchWidgetProvider.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.PendingIntent
+import android.app.SearchManager
+import android.appwidget.AppWidgetManager
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.RemoteViews
+import com.android.common.Search
+
+/** Search widget provider. */
+class SearchWidgetProvider : BroadcastReceiver() {
+ @Override
+ override fun onReceive(context: Context?, intent: Intent) {
+ if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0).toString() + ")")
+ val action: String? = intent.getAction()
+ if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
+ // nothing needs doing
+ } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ updateSearchWidgets(context)
+ } else {
+ if (DBG) Log.d(TAG, "Unhandled intent action=$action")
+ }
+ }
+
+ private class SearchWidgetState(private val mAppWidgetId: Int) {
+ private var mQueryTextViewIntent: Intent? = null
+ private var mVoiceSearchIntent: Intent? = null
+ fun setQueryTextViewIntent(queryTextViewIntent: Intent?) {
+ mQueryTextViewIntent = queryTextViewIntent
+ }
+
+ fun setVoiceSearchIntent(voiceSearchIntent: Intent?) {
+ mVoiceSearchIntent = voiceSearchIntent
+ }
+
+ fun updateWidget(context: Context?, appWidgetMgr: AppWidgetManager) {
+ if (DBG) Log.d(TAG, "Updating appwidget $mAppWidgetId")
+ val views = RemoteViews(context!!.getPackageName(), R.layout.search_widget)
+ setOnClickActivityIntent(context, views, R.id.search_widget_text, mQueryTextViewIntent)
+ // Voice Search button
+ if (mVoiceSearchIntent != null) {
+ setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn, mVoiceSearchIntent)
+ views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE)
+ } else {
+ views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE)
+ }
+ appWidgetMgr.updateAppWidget(mAppWidgetId, views)
+ }
+
+ private fun setOnClickActivityIntent(
+ context: Context?,
+ views: RemoteViews,
+ viewId: Int,
+ intent: Intent?
+ ) {
+ intent?.setPackage(context?.getPackageName())
+ val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
+ views.setOnClickPendingIntent(viewId, pendingIntent)
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SearchWidgetProvider"
+
+ /** The [Search.SOURCE] value used when starting searches from the search widget. */
+ private const val WIDGET_SEARCH_SOURCE = "launcher-widget"
+ private fun getSearchWidgetStates(context: Context?): Array<SearchWidgetState?> {
+ val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
+ val appWidgetIds: IntArray = appWidgetManager.getAppWidgetIds(myComponentName(context))
+ val states: Array<SearchWidgetState?> = arrayOfNulls(appWidgetIds.size)
+ for (i in appWidgetIds.indices) {
+ states[i] = getSearchWidgetState(context, appWidgetIds[i])
+ }
+ return states
+ }
+
+ /** Updates all search widgets. */
+ @JvmStatic
+ fun updateSearchWidgets(context: Context?) {
+ if (DBG) Log.d(TAG, "updateSearchWidgets")
+ val states: Array<SearchWidgetState?> = getSearchWidgetStates(context)
+ for (state in states) {
+ state?.updateWidget(context, AppWidgetManager.getInstance(context))
+ }
+ }
+
+ /** Gets the component name of this search widget provider. */
+ private fun myComponentName(context: Context?): ComponentName {
+ val pkg: String = context!!.getPackageName()
+ val cls = "$pkg.SearchWidgetProvider"
+ return ComponentName(pkg, cls)
+ }
+
+ private fun createQsbActivityIntent(
+ context: Context?,
+ action: String,
+ widgetAppData: Bundle
+ ): Intent {
+ val qsbIntent = Intent(action)
+ qsbIntent.setPackage(context?.getPackageName())
+ qsbIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ )
+ qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData)
+ return qsbIntent
+ }
+
+ private fun getSearchWidgetState(context: Context?, appWidgetId: Int): SearchWidgetState {
+ if (DBG) Log.d(TAG, "Creating appwidget state $appWidgetId")
+ val state: SearchWidgetState = SearchWidgetState(appWidgetId)
+ val widgetAppData = Bundle()
+ widgetAppData.putString(Search.SOURCE, WIDGET_SEARCH_SOURCE)
+
+ // Text field click
+ val qsbIntent: Intent =
+ createQsbActivityIntent(context, SearchManager.INTENT_ACTION_GLOBAL_SEARCH, widgetAppData)
+ state.setQueryTextViewIntent(qsbIntent)
+
+ // Voice search button
+ val voiceSearchIntent: Intent? = getVoiceSearchIntent(context, widgetAppData)
+ state.setVoiceSearchIntent(voiceSearchIntent)
+ return state
+ }
+
+ private fun getVoiceSearchIntent(context: Context?, widgetAppData: Bundle): Intent? {
+ val voiceSearch: VoiceSearch? = QsbApplication[context].voiceSearch
+ return voiceSearch?.createVoiceWebSearchIntent(widgetAppData)
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/Source.java b/src/com/android/quicksearchbox/Source.java
deleted file mode 100644
index 680f415..0000000
--- a/src/com/android/quicksearchbox/Source.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.NowOrLater;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * Interface for suggestion sources.
- *
- */
-public interface Source extends SuggestionCursorProvider<SourceResult> {
-
- /**
- * Gets the name activity that intents from this source are sent to.
- */
- ComponentName getIntentComponent();
-
- /**
- * Gets the suggestion URI for getting suggestions from this Source.
- */
- String getSuggestUri();
-
- /**
- * Gets the localized, human-readable label for this source.
- */
- CharSequence getLabel();
-
- /**
- * Gets the icon for this suggestion source.
- */
- Drawable getSourceIcon();
-
- /**
- * Gets the icon URI for this suggestion source.
- */
- Uri getSourceIconUri();
-
- /**
- * Gets an icon from this suggestion source.
- *
- * @param drawableId Resource ID or URI.
- */
- NowOrLater<Drawable> getIcon(String drawableId);
-
- /**
- * Gets the URI for an icon form this suggestion source.
- *
- * @param drawableId Resource ID or URI.
- */
- Uri getIconUri(String drawableId);
-
- /**
- * Gets the search hint text for this suggestion source.
- */
- CharSequence getHint();
-
- /**
- * Gets the description to use for this source in system search settings.
- */
- CharSequence getSettingsDescription();
-
- /**
- *
- * Note: this does not guarantee that this source will be queried for queries of
- * this length or longer, only that it will not be queried for anything shorter.
- *
- * @return The minimum number of characters needed to trigger this source.
- */
- int getQueryThreshold();
-
- /**
- * Indicates whether a source should be invoked for supersets of queries it has returned zero
- * results for in the past. For example, if a source returned zero results for "bo", it would
- * be ignored for "bob".
- *
- * If set to <code>false</code>, this source will only be ignored for a single session; the next
- * time the search dialog is brought up, all sources will be queried.
- *
- * @return <code>true</code> if this source should be queried after returning no results.
- */
- boolean queryAfterZeroResults();
-
- boolean voiceSearchEnabled();
-
- /**
- * Whether this source should be included in the blended All mode. The source must
- * also be enabled to be included in All.
- */
- boolean includeInAll();
-
- Intent createSearchIntent(String query, Bundle appData);
-
- Intent createVoiceSearchIntent(Bundle appData);
-
- /**
- * Checks if the current process can read the suggestions from this source.
- */
- boolean canRead();
-
- /**
- * Gets suggestions from this source.
- *
- * @param query The user query.
- * @return The suggestion results.
- */
- @Override
- SourceResult getSuggestions(String query, int queryLimit);
-
- /**
- * Gets the default intent action for suggestions from this source.
- *
- * @return The default intent action, or {@code null}.
- */
- String getDefaultIntentAction();
-
- /**
- * Gets the default intent data for suggestions from this source.
- *
- * @return The default intent data, or {@code null}.
- */
- String getDefaultIntentData();
-
- /**
- * Gets the root source, if this source is a wrapper around another. Otherwise, returns this
- * source.
- */
- Source getRoot();
-
-}
diff --git a/src/com/android/quicksearchbox/Source.kt b/src/com/android/quicksearchbox/Source.kt
new file mode 100644
index 0000000..cdc0a79
--- /dev/null
+++ b/src/com/android/quicksearchbox/Source.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Bundle
+import com.android.quicksearchbox.util.NowOrLater
+
+/** Interface for suggestion sources. */
+interface Source : SuggestionCursorProvider<com.android.quicksearchbox.SourceResult?> {
+ /** Gets the name activity that intents from this source are sent to. */
+ val intentComponent: ComponentName?
+
+ /** Gets the suggestion URI for getting suggestions from this Source. */
+ val suggestUri: String?
+
+ /** Gets the localized, human-readable label for this source. */
+ val label: CharSequence?
+
+ /** Gets the icon for this suggestion source. */
+ val sourceIcon: Drawable?
+
+ /** Gets the icon URI for this suggestion source. */
+ val sourceIconUri: Uri?
+
+ /**
+ * Gets an icon from this suggestion source.
+ *
+ * @param drawableId Resource ID or URI.
+ */
+ fun getIcon(drawableId: String?): NowOrLater<Drawable?>?
+
+ /**
+ * Gets the URI for an icon form this suggestion source.
+ *
+ * @param drawableId Resource ID or URI.
+ */
+ fun getIconUri(drawableId: String?): Uri?
+
+ /** Gets the search hint text for this suggestion source. */
+ val hint: CharSequence?
+
+ /** Gets the description to use for this source in system search settings. */
+ val settingsDescription: CharSequence?
+
+ /**
+ *
+ * Note: this does not guarantee that this source will be queried for queries of this length or
+ * longer, only that it will not be queried for anything shorter.
+ *
+ * @return The minimum number of characters needed to trigger this source.
+ */
+ val queryThreshold: Int
+
+ /**
+ * Indicates whether a source should be invoked for supersets of queries it has returned zero
+ * results for in the past. For example, if a source returned zero results for "bo", it would be
+ * ignored for "bob".
+ *
+ * If set to `false`, this source will only be ignored for a single session; the next time the
+ * search dialog is brought up, all sources will be queried.
+ *
+ * @return `true` if this source should be queried after returning no results.
+ */
+ fun queryAfterZeroResults(): Boolean
+ fun voiceSearchEnabled(): Boolean
+
+ /**
+ * Whether this source should be included in the blended All mode. The source must also be enabled
+ * to be included in All.
+ */
+ fun includeInAll(): Boolean
+ fun createSearchIntent(query: String?, appData: Bundle?): Intent?
+ fun createVoiceSearchIntent(appData: Bundle?): Intent?
+
+ /** Checks if the current process can read the suggestions from this source. */
+ fun canRead(): Boolean
+
+ /**
+ * Gets suggestions from this source.
+ *
+ * @param query The user query.
+ * @return The suggestion results.
+ */
+ @Override override fun getSuggestions(query: String?, queryLimit: Int): SourceResult?
+
+ /**
+ * Gets the default intent action for suggestions from this source.
+ *
+ * @return The default intent action, or `null`.
+ */
+ val defaultIntentAction: String?
+
+ /**
+ * Gets the default intent data for suggestions from this source.
+ *
+ * @return The default intent data, or `null`.
+ */
+ val defaultIntentData: String?
+
+ /**
+ * Gets the root source, if this source is a wrapper around another. Otherwise, returns this
+ * source.
+ */
+ fun getRoot(): Source
+}
diff --git a/src/com/android/quicksearchbox/SourceResult.java b/src/com/android/quicksearchbox/SourceResult.kt
index 20ea48f..b5baae3 100644
--- a/src/com/android/quicksearchbox/SourceResult.java
+++ b/src/com/android/quicksearchbox/SourceResult.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,14 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.quicksearchbox
-package com.android.quicksearchbox;
-
-/**
- * The result of getting suggestions from a single source.
- */
-public interface SourceResult extends SuggestionCursor {
-
- Source getSource();
-
+/** The result of getting suggestions from a single source. */
+interface SourceResult : SuggestionCursor {
+ val source: Source?
}
diff --git a/src/com/android/quicksearchbox/Suggestion.java b/src/com/android/quicksearchbox/Suggestion.java
deleted file mode 100644
index 81f5578..0000000
--- a/src/com/android/quicksearchbox/Suggestion.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.content.ComponentName;
-
-/**
- * Interface for individual suggestions.
- */
-public interface Suggestion {
-
- /**
- * Gets the source that produced the current suggestion.
- */
- Source getSuggestionSource();
-
- /**
- * Gets the shortcut ID of the current suggestion.
- */
- String getShortcutId();
-
- /**
- * Whether to show a spinner while refreshing this shortcut.
- */
- boolean isSpinnerWhileRefreshing();
-
- /**
- * Gets the format of the text returned by {@link #getSuggestionText1()}
- * and {@link #getSuggestionText2()}.
- *
- * @return {@code null} or "html"
- */
- String getSuggestionFormat();
-
- /**
- * Gets the first text line for the current suggestion.
- */
- String getSuggestionText1();
-
- /**
- * Gets the second text line for the current suggestion.
- */
- String getSuggestionText2();
-
- /**
- * Gets the second text line URL for the current suggestion.
- */
- String getSuggestionText2Url();
-
- /**
- * Gets the left-hand-side icon for the current suggestion.
- *
- * @return A string that can be passed to {@link Source#getIcon(String)}.
- */
- String getSuggestionIcon1();
-
- /**
- * Gets the right-hand-side icon for the current suggestion.
- *
- * @return A string that can be passed to {@link Source#getIcon(String)}.
- */
- String getSuggestionIcon2();
-
- /**
- * Gets the intent action for the current suggestion.
- */
- String getSuggestionIntentAction();
-
- /**
- * Gets the name of the activity that the intent for the current suggestion will be sent to.
- */
- ComponentName getSuggestionIntentComponent();
-
- /**
- * Gets the extra data associated with this suggestion's intent.
- */
- String getSuggestionIntentExtraData();
-
- /**
- * Gets the data associated with this suggestion's intent.
- */
- String getSuggestionIntentDataString();
-
- /**
- * Gets the query associated with this suggestion's intent.
- */
- String getSuggestionQuery();
-
- /**
- * Gets the suggestion log type for the current suggestion. This is logged together
- * with the value returned from {@link Source#getName()}.
- * The value is source-specific. Most sources return {@code null}.
- */
- String getSuggestionLogType();
-
- /**
- * Checks if this suggestion is a shortcut.
- */
- boolean isSuggestionShortcut();
-
- /**
- * Checks if this is a web search suggestion.
- */
- boolean isWebSearchSuggestion();
-
- /**
- * Checks whether this suggestion comes from the user's search history.
- */
- boolean isHistorySuggestion();
-
- /**
- * Returns any extras associated with this suggestion, or {@code null} if there are none.
- */
- SuggestionExtras getExtras();
-
-}
diff --git a/src/com/android/quicksearchbox/Suggestion.kt b/src/com/android/quicksearchbox/Suggestion.kt
new file mode 100644
index 0000000..2f82a15
--- /dev/null
+++ b/src/com/android/quicksearchbox/Suggestion.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ComponentName
+
+/** Interface for individual suggestions. */
+interface Suggestion {
+ /** Gets the source that produced the current suggestion. */
+ val suggestionSource: com.android.quicksearchbox.Source?
+
+ /** Gets the shortcut ID of the current suggestion. */
+ val shortcutId: String?
+
+ /** Whether to show a spinner while refreshing this shortcut. */
+ val isSpinnerWhileRefreshing: Boolean
+
+ /**
+ * Gets the format of the text returned by [.getSuggestionText1] and [.getSuggestionText2].
+ *
+ * @return `null` or "html"
+ */
+ val suggestionFormat: String?
+
+ /** Gets the first text line for the current suggestion. */
+ val suggestionText1: String?
+
+ /** Gets the second text line for the current suggestion. */
+ val suggestionText2: String?
+
+ /** Gets the second text line URL for the current suggestion. */
+ val suggestionText2Url: String?
+
+ /**
+ * Gets the left-hand-side icon for the current suggestion.
+ *
+ * @return A string that can be passed to [Source.getIcon].
+ */
+ val suggestionIcon1: String?
+
+ /**
+ * Gets the right-hand-side icon for the current suggestion.
+ *
+ * @return A string that can be passed to [Source.getIcon].
+ */
+ val suggestionIcon2: String?
+
+ /** Gets the intent action for the current suggestion. */
+ val suggestionIntentAction: String?
+
+ /** Gets the name of the activity that the intent for the current suggestion will be sent to. */
+ val suggestionIntentComponent: ComponentName?
+
+ /** Gets the extra data associated with this suggestion's intent. */
+ val suggestionIntentExtraData: String?
+
+ /** Gets the data associated with this suggestion's intent. */
+ val suggestionIntentDataString: String?
+
+ /** Gets the query associated with this suggestion's intent. */
+ val suggestionQuery: String?
+
+ /**
+ * Gets the suggestion log type for the current suggestion. This is logged together with the value
+ * returned from [Source.getName]. The value is source-specific. Most sources return `null`.
+ */
+ val suggestionLogType: String?
+
+ /** Checks if this suggestion is a shortcut. */
+ val isSuggestionShortcut: Boolean
+
+ /** Checks if this is a web search suggestion. */
+ val isWebSearchSuggestion: Boolean
+
+ /** Checks whether this suggestion comes from the user's search history. */
+ val isHistorySuggestion: Boolean
+
+ /** Returns any extras associated with this suggestion, or `null` if there are none. */
+ val extras: com.android.quicksearchbox.SuggestionExtras?
+}
diff --git a/src/com/android/quicksearchbox/SuggestionCursor.java b/src/com/android/quicksearchbox/SuggestionCursor.java
deleted file mode 100644
index 04d53c8..0000000
--- a/src/com/android/quicksearchbox/SuggestionCursor.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import com.android.quicksearchbox.util.QuietlyCloseable;
-
-import android.database.DataSetObserver;
-
-import java.util.Collection;
-
-/**
- * A sequence of suggestions, with a current position.
- */
-public interface SuggestionCursor extends Suggestion, QuietlyCloseable {
-
- /**
- * Gets the query that the user typed to get this suggestion.
- */
- String getUserQuery();
-
- /**
- * Gets the number of suggestions in this result.
- *
- * @return The number of suggestions, or {@code 0} if this result represents a failed query.
- */
- int getCount();
-
- /**
- * Moves to a given suggestion.
- *
- * @param pos The position to move to.
- * @throws IndexOutOfBoundsException if {@code pos < 0} or {@code pos >= getCount()}.
- */
- void moveTo(int pos);
-
- /**
- * Moves to the next suggestion, if there is one.
- *
- * @return {@code false} if there is no next suggestion.
- */
- boolean moveToNext();
-
- /**
- * Gets the current position within the cursor.
- */
- int getPosition();
-
- /**
- * Frees any resources used by this cursor.
- */
- @Override
- void close();
-
- /**
- * Register an observer that is called when changes happen to this data set.
- *
- * @param observer gets notified when the data set changes.
- */
- void registerDataSetObserver(DataSetObserver observer);
-
- /**
- * Unregister an observer that has previously been registered with
- * {@link #registerDataSetObserver(DataSetObserver)}
- *
- * @param observer the observer to unregister.
- */
- void unregisterDataSetObserver(DataSetObserver observer);
-
- /**
- * Return the extra columns present in this cursor, or null if none exist.
- */
- Collection<String> getExtraColumns();
-}
diff --git a/src/com/android/quicksearchbox/SuggestionCursor.kt b/src/com/android/quicksearchbox/SuggestionCursor.kt
new file mode 100644
index 0000000..40c704b
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionCursor.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.database.DataSetObserver
+import com.android.quicksearchbox.util.QuietlyCloseable
+import kotlin.collections.Collection
+
+/** A sequence of suggestions, with a current position. */
+interface SuggestionCursor : Suggestion, QuietlyCloseable {
+ /** Gets the query that the user typed to get this suggestion. */
+ val userQuery: String?
+
+ /**
+ * Gets the number of suggestions in this result.
+ *
+ * @return The number of suggestions, or `0` if this result represents a failed query.
+ */
+ val count: Int
+
+ /**
+ * Moves to a given suggestion.
+ *
+ * @param pos The position to move to.
+ * @throws IndexOutOfBoundsException if `pos < 0` or `pos >= getCount()`.
+ */
+ fun moveTo(pos: Int)
+
+ /**
+ * Moves to the next suggestion, if there is one.
+ *
+ * @return `false` if there is no next suggestion.
+ */
+ fun moveToNext(): Boolean
+
+ /** Gets the current position within the cursor. */
+ val position: Int
+
+ /** Frees any resources used by this cursor. */
+ @Override override fun close()
+
+ /**
+ * Register an observer that is called when changes happen to this data set.
+ *
+ * @param observer gets notified when the data set changes.
+ */
+ fun registerDataSetObserver(observer: DataSetObserver?)
+
+ /**
+ * Unregister an observer that has previously been registered with [.registerDataSetObserver]
+ *
+ * @param observer the observer to unregister.
+ */
+ fun unregisterDataSetObserver(observer: DataSetObserver?)
+
+ /** Return the extra columns present in this cursor, or null if none exist. */
+ val extraColumns: Collection<String>?
+}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.java b/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.java
deleted file mode 100644
index 7e929c5..0000000
--- a/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.app.SearchManager;
-import android.database.AbstractCursor;
-import android.database.CursorIndexOutOfBoundsException;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-
-public class SuggestionCursorBackedCursor extends AbstractCursor {
-
- // This array also used in CursorBackedSuggestionExtras to avoid duplication.
- public static final String[] COLUMNS = {
- "_id", // 0, This will contain the row number. CursorAdapter, used by SuggestionsAdapter,
- // used by SearchDialog, expects an _id column.
- SearchManager.SUGGEST_COLUMN_TEXT_1, // 1
- SearchManager.SUGGEST_COLUMN_TEXT_2, // 2
- SearchManager.SUGGEST_COLUMN_TEXT_2_URL, // 3
- SearchManager.SUGGEST_COLUMN_ICON_1, // 4
- SearchManager.SUGGEST_COLUMN_ICON_2, // 5
- SearchManager.SUGGEST_COLUMN_INTENT_ACTION, // 6
- SearchManager.SUGGEST_COLUMN_INTENT_DATA, // 7
- SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, // 8
- SearchManager.SUGGEST_COLUMN_QUERY, // 9
- SearchManager.SUGGEST_COLUMN_FORMAT, // 10
- SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, // 11
- SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING, // 12
- };
-
- private static final int COLUMN_INDEX_ID = 0;
- private static final int COLUMN_INDEX_TEXT1 = 1;
- private static final int COLUMN_INDEX_TEXT2 = 2;
- private static final int COLUMN_INDEX_TEXT2_URL = 3;
- private static final int COLUMN_INDEX_ICON1 = 4;
- private static final int COLUMN_INDEX_ICON2 = 5;
- private static final int COLUMN_INDEX_INTENT_ACTION = 6;
- private static final int COLUMN_INDEX_INTENT_DATA = 7;
- private static final int COLUMN_INDEX_INTENT_EXTRA_DATA = 8;
- private static final int COLUMN_INDEX_QUERY = 9;
- private static final int COLUMN_INDEX_FORMAT = 10;
- private static final int COLUMN_INDEX_SHORTCUT_ID = 11;
- private static final int COLUMN_INDEX_SPINNER_WHILE_REFRESHING = 12;
-
- private final SuggestionCursor mCursor;
- private ArrayList<String> mExtraColumns;
-
- public SuggestionCursorBackedCursor(SuggestionCursor cursor) {
- mCursor = cursor;
- }
-
- @Override
- public void close() {
- super.close();
- mCursor.close();
- }
-
- @Override
- public String[] getColumnNames() {
- Collection<String> extraColumns = mCursor.getExtraColumns();
- if (extraColumns != null) {
- ArrayList<String> allColumns = new ArrayList<String>(COLUMNS.length +
- extraColumns.size());
- mExtraColumns = new ArrayList<String>(extraColumns);
- allColumns.addAll(Arrays.asList(COLUMNS));
- allColumns.addAll(mExtraColumns);
- return allColumns.toArray(new String[allColumns.size()]);
- } else {
- return COLUMNS;
- }
- }
-
- @Override
- public int getCount() {
- return mCursor.getCount();
- }
-
- private Suggestion get() {
- mCursor.moveTo(getPosition());
- return mCursor;
- }
-
- private String getExtra(int columnIdx) {
- int extraColumn = columnIdx - COLUMNS.length;
- SuggestionExtras extras = get().getExtras();
- if (extras != null) {
- return extras.getExtra(mExtraColumns.get(extraColumn));
- } else {
- return null;
- }
- }
-
- @Override
- public int getInt(int column) {
- if (column == COLUMN_INDEX_ID) {
- return getPosition();
- } else {
- try {
- return Integer.valueOf(getString(column));
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
-
- @Override
- public String getString(int column) {
- if (column < COLUMNS.length) {
- switch (column) {
- case COLUMN_INDEX_ID:
- return String.valueOf(getPosition());
- case COLUMN_INDEX_TEXT1:
- return get().getSuggestionText1();
- case COLUMN_INDEX_TEXT2:
- return get().getSuggestionText2();
- case COLUMN_INDEX_TEXT2_URL:
- return get().getSuggestionText2Url();
- case COLUMN_INDEX_ICON1:
- return get().getSuggestionIcon1();
- case COLUMN_INDEX_ICON2:
- return get().getSuggestionIcon2();
- case COLUMN_INDEX_INTENT_ACTION:
- return get().getSuggestionIntentAction();
- case COLUMN_INDEX_INTENT_DATA:
- return get().getSuggestionIntentDataString();
- case COLUMN_INDEX_INTENT_EXTRA_DATA:
- return get().getSuggestionIntentExtraData();
- case COLUMN_INDEX_QUERY:
- return get().getSuggestionQuery();
- case COLUMN_INDEX_FORMAT:
- return get().getSuggestionFormat();
- case COLUMN_INDEX_SHORTCUT_ID:
- return get().getShortcutId();
- case COLUMN_INDEX_SPINNER_WHILE_REFRESHING:
- return String.valueOf(get().isSpinnerWhileRefreshing());
- default:
- throw new CursorIndexOutOfBoundsException("Requested column " + column
- + " of " + COLUMNS.length);
- }
- } else {
- return getExtra(column);
- }
- }
-
- @Override
- public long getLong(int column) {
- try {
- return Long.valueOf(getString(column));
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
- @Override
- public boolean isNull(int column) {
- return getString(column) == null;
- }
-
- @Override
- public short getShort(int column) {
- try {
- return Short.valueOf(getString(column));
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
- @Override
- public double getDouble(int column) {
- try {
- return Double.valueOf(getString(column));
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
- @Override
- public float getFloat(int column) {
- try {
- return Float.valueOf(getString(column));
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.kt b/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.kt
new file mode 100644
index 0000000..9ee2b06
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionCursorBackedCursor.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.database.AbstractCursor
+import android.database.CursorIndexOutOfBoundsException
+import kotlin.collections.ArrayList
+
+class SuggestionCursorBackedCursor(private val mCursor: SuggestionCursor?) : AbstractCursor() {
+ private var mExtraColumns: ArrayList<String>? = null
+
+ @Override
+ override fun close() {
+ super.close()
+ mCursor?.close()
+ }
+
+ @Override
+ override fun getColumnNames(): Array<String> {
+ val extraColumns: Collection<String>? = mCursor?.extraColumns
+ return if (extraColumns != null) {
+ val allColumns: ArrayList<String> = ArrayList<String>(COLUMNS.size + extraColumns.size)
+ mExtraColumns = ArrayList<String>(extraColumns)
+ allColumns.addAll(COLUMNS.asList())
+ mExtraColumns?.let { allColumns.addAll(it) }
+ allColumns.toArray(arrayOfNulls<String>(allColumns.size))
+ } else {
+ COLUMNS
+ }
+ }
+
+ @Override
+ override fun getCount(): Int {
+ return mCursor!!.count
+ }
+
+ private fun get(): SuggestionCursor? {
+ mCursor?.moveTo(position)
+ return mCursor
+ }
+
+ private fun getExtra(columnIdx: Int): String? {
+ val extraColumn = columnIdx - COLUMNS.size
+ val extras: SuggestionExtras? = get()?.extras
+ return extras?.getExtra(mExtraColumns!!.get(extraColumn))
+ }
+
+ @Override
+ override fun getInt(column: Int): Int {
+ return if (column == COLUMN_INDEX_ID) {
+ position
+ } else {
+ try {
+ getString(column)!!.toInt()
+ } catch (e: NumberFormatException) {
+ 0
+ }
+ }
+ }
+
+ @Override
+ override fun getString(column: Int): String? {
+ return if (column < COLUMNS.size) {
+ when (column) {
+ COLUMN_INDEX_ID -> position.toString()
+ COLUMN_INDEX_TEXT1 -> get()?.suggestionText1
+ COLUMN_INDEX_TEXT2 -> get()?.suggestionText2
+ COLUMN_INDEX_TEXT2_URL -> get()?.suggestionText2Url
+ COLUMN_INDEX_ICON1 -> get()?.suggestionIcon1
+ COLUMN_INDEX_ICON2 -> get()?.suggestionIcon2
+ COLUMN_INDEX_INTENT_ACTION -> get()?.suggestionIntentAction
+ COLUMN_INDEX_INTENT_DATA -> get()?.suggestionIntentDataString
+ COLUMN_INDEX_INTENT_EXTRA_DATA -> get()?.suggestionIntentExtraData
+ COLUMN_INDEX_QUERY -> get()?.suggestionQuery
+ COLUMN_INDEX_FORMAT -> get()?.suggestionFormat
+ COLUMN_INDEX_SHORTCUT_ID -> get()?.shortcutId
+ COLUMN_INDEX_SPINNER_WHILE_REFRESHING -> get()?.isSpinnerWhileRefreshing.toString()
+ else ->
+ throw CursorIndexOutOfBoundsException(
+ "Requested column " + column + " of " + COLUMNS.size
+ )
+ }
+ } else {
+ getExtra(column)
+ }
+ }
+
+ @Override
+ override fun getLong(column: Int): Long {
+ return try {
+ getString(column)!!.toLong()
+ } catch (e: NumberFormatException) {
+ 0
+ }
+ }
+
+ @Override
+ override fun isNull(column: Int): Boolean {
+ return getString(column) == null
+ }
+
+ @Override
+ override fun getShort(column: Int): Short {
+ return try {
+ getString(column)!!.toShort()
+ } catch (e: NumberFormatException) {
+ 0
+ }
+ }
+
+ @Override
+ override fun getDouble(column: Int): Double {
+ return try {
+ getString(column)!!.toDouble()
+ } catch (e: NumberFormatException) {
+ 0.0
+ }
+ }
+
+ @Override
+ override fun getFloat(column: Int): Float {
+ return try {
+ getString(column)!!.toFloat()
+ } catch (e: NumberFormatException) {
+ 0.0F
+ }
+ }
+
+ companion object {
+ // This array also used in CursorBackedSuggestionExtras to avoid duplication.
+ val COLUMNS =
+ arrayOf(
+ "_id", // 0, This will contain the row number. CursorAdapter, used by SuggestionsAdapter,
+ // used by SearchDialog, expects an _id column.
+ SearchManager.SUGGEST_COLUMN_TEXT_1, // 1
+ SearchManager.SUGGEST_COLUMN_TEXT_2, // 2
+ SearchManager.SUGGEST_COLUMN_TEXT_2_URL, // 3
+ SearchManager.SUGGEST_COLUMN_ICON_1, // 4
+ SearchManager.SUGGEST_COLUMN_ICON_2, // 5
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION, // 6
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA, // 7
+ SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, // 8
+ SearchManager.SUGGEST_COLUMN_QUERY, // 9
+ SearchManager.SUGGEST_COLUMN_FORMAT, // 10
+ SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, // 11
+ SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING
+ )
+ private const val COLUMN_INDEX_ID = 0
+ private const val COLUMN_INDEX_TEXT1 = 1
+ private const val COLUMN_INDEX_TEXT2 = 2
+ private const val COLUMN_INDEX_TEXT2_URL = 3
+ private const val COLUMN_INDEX_ICON1 = 4
+ private const val COLUMN_INDEX_ICON2 = 5
+ private const val COLUMN_INDEX_INTENT_ACTION = 6
+ private const val COLUMN_INDEX_INTENT_DATA = 7
+ private const val COLUMN_INDEX_INTENT_EXTRA_DATA = 8
+ private const val COLUMN_INDEX_QUERY = 9
+ private const val COLUMN_INDEX_FORMAT = 10
+ private const val COLUMN_INDEX_SHORTCUT_ID = 11
+ private const val COLUMN_INDEX_SPINNER_WHILE_REFRESHING = 12
+ }
+}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorProvider.java b/src/com/android/quicksearchbox/SuggestionCursorProvider.java
deleted file mode 100644
index 23109cd..0000000
--- a/src/com/android/quicksearchbox/SuggestionCursorProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-
-/**
- * Interface for objects that can produce a SuggestionCursor given a query.
- */
-public interface SuggestionCursorProvider<C extends SuggestionCursor> {
-
- /**
- * Gets the name of the provider. This is used for logging and
- * to execute tasks on the queue for the provider.
- */
- String getName();
-
- /**
- * Gets suggestions from the provider.
- *
- * @param query The user query.
- * @param queryLimit An advisory maximum number of results that the source should return.
- * @return The suggestion results. Must not be {@code null}.
- */
- C getSuggestions(String query, int queryLimit);
-}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorProvider.kt b/src/com/android/quicksearchbox/SuggestionCursorProvider.kt
new file mode 100644
index 0000000..3ea7b93
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionCursorProvider.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** Interface for objects that can produce a SuggestionCursor given a query. */
+interface SuggestionCursorProvider<C : SuggestionCursor?> {
+ /**
+ * Gets the name of the provider. This is used for logging and to execute tasks on the queue for
+ * the provider.
+ */
+ val name: String?
+
+ /**
+ * Gets suggestions from the provider.
+ *
+ * @param query The user query.
+ * @param queryLimit An advisory maximum number of results that the source should return.
+ * @return The suggestion results. Must not be `null`.
+ */
+ fun getSuggestions(query: String?, queryLimit: Int): C?
+}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorWrapper.java b/src/com/android/quicksearchbox/SuggestionCursorWrapper.java
deleted file mode 100644
index 83e74f4..0000000
--- a/src/com/android/quicksearchbox/SuggestionCursorWrapper.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.database.DataSetObserver;
-
-import java.util.Collection;
-
-/**
- * A suggestion cursor that delegates all methods to another SuggestionCursor.
- */
-public class SuggestionCursorWrapper extends AbstractSuggestionCursorWrapper {
-
- private final SuggestionCursor mCursor;
-
- public SuggestionCursorWrapper(String userQuery, SuggestionCursor cursor) {
- super(userQuery);
- mCursor = cursor;
- }
-
- public void close() {
- if (mCursor != null) {
- mCursor.close();
- }
- }
-
- public int getCount() {
- return mCursor == null ? 0 : mCursor.getCount();
- }
-
- public int getPosition() {
- return mCursor == null ? 0 : mCursor.getPosition();
- }
-
- public void moveTo(int pos) {
- if (mCursor != null) {
- mCursor.moveTo(pos);
- }
- }
-
- public boolean moveToNext() {
- if (mCursor != null) {
- return mCursor.moveToNext();
- } else {
- return false;
- }
- }
-
- public void registerDataSetObserver(DataSetObserver observer) {
- if (mCursor != null) {
- mCursor.registerDataSetObserver(observer);
- }
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- if (mCursor != null) {
- mCursor.unregisterDataSetObserver(observer);
- }
- }
-
- @Override
- protected SuggestionCursor current() {
- return mCursor;
- }
-
- public Collection<String> getExtraColumns() {
- return mCursor.getExtraColumns();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorWrapper.kt b/src/com/android/quicksearchbox/SuggestionCursorWrapper.kt
new file mode 100644
index 0000000..c09ea1a
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionCursorWrapper.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.database.DataSetObserver
+
+/** A suggestion cursor that delegates all methods to another SuggestionCursor. */
+open class SuggestionCursorWrapper(userQuery: String?, private val mCursor: SuggestionCursor?) :
+ AbstractSuggestionCursorWrapper(userQuery!!) {
+ override fun close() {
+ if (mCursor != null) {
+ mCursor.close()
+ }
+ }
+
+ override val count: Int
+ get() = if (mCursor == null) 0 else mCursor.count
+ override val position: Int
+ get() = if (mCursor == null) 0 else mCursor.position
+
+ override fun moveTo(pos: Int) {
+ if (mCursor != null) {
+ mCursor.moveTo(pos)
+ }
+ }
+
+ override fun moveToNext(): Boolean {
+ return mCursor?.moveToNext() ?: false
+ }
+
+ override fun registerDataSetObserver(observer: DataSetObserver?) {
+ if (mCursor != null) {
+ mCursor.registerDataSetObserver(observer)
+ }
+ }
+
+ override fun unregisterDataSetObserver(observer: DataSetObserver?) {
+ if (mCursor != null) {
+ mCursor.unregisterDataSetObserver(observer)
+ }
+ }
+
+ @Override
+ override fun current(): SuggestionCursor {
+ return mCursor!!
+ }
+
+ override val extraColumns: Collection<String>?
+ get() = mCursor?.extraColumns
+}
diff --git a/src/com/android/quicksearchbox/SuggestionData.java b/src/com/android/quicksearchbox/SuggestionData.java
deleted file mode 100644
index 3cc835d..0000000
--- a/src/com/android/quicksearchbox/SuggestionData.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.content.ComponentName;
-import android.content.Intent;
-
-
-/**
- * Holds data for each suggest item including the display data and how to launch the result.
- * Used for passing from the provider to the suggest cursor.
- */
-public class SuggestionData implements Suggestion {
-
- private final Source mSource;
- private String mFormat;
- private String mText1;
- private String mText2;
- private String mText2Url;
- private String mIcon1;
- private String mIcon2;
- private String mShortcutId;
- private boolean mSpinnerWhileRefreshing;
- private String mIntentAction;
- private String mIntentData;
- private String mIntentExtraData;
- private String mSuggestionQuery;
- private String mLogType;
- private boolean mIsShortcut;
- private boolean mIsHistory;
- private SuggestionExtras mExtras;
-
- public SuggestionData(Source source) {
- mSource = source;
- }
-
- public Source getSuggestionSource() {
- return mSource;
- }
-
- public String getSuggestionFormat() {
- return mFormat;
- }
-
- public String getSuggestionText1() {
- return mText1;
- }
-
- public String getSuggestionText2() {
- return mText2;
- }
-
- public String getSuggestionText2Url() {
- return mText2Url;
- }
-
- public String getSuggestionIcon1() {
- return mIcon1;
- }
-
- public String getSuggestionIcon2() {
- return mIcon2;
- }
-
- public boolean isSpinnerWhileRefreshing() {
- return mSpinnerWhileRefreshing;
- }
-
- public String getIntentExtraData() {
- return mIntentExtraData;
- }
-
- public String getShortcutId() {
- return mShortcutId;
- }
-
- public String getSuggestionIntentAction() {
- if (mIntentAction != null) return mIntentAction;
- return mSource.getDefaultIntentAction();
- }
-
- public ComponentName getSuggestionIntentComponent() {
- return mSource.getIntentComponent();
- }
-
- public String getSuggestionIntentDataString() {
- return mIntentData;
- }
-
- public String getSuggestionIntentExtraData() {
- return mIntentExtraData;
- }
-
- public String getSuggestionQuery() {
- return mSuggestionQuery;
- }
-
- public String getSuggestionLogType() {
- return mLogType;
- }
-
- public boolean isSuggestionShortcut() {
- return mIsShortcut;
- }
-
- public boolean isWebSearchSuggestion() {
- return Intent.ACTION_WEB_SEARCH.equals(getSuggestionIntentAction());
- }
-
- public boolean isHistorySuggestion() {
- return mIsHistory;
- }
-
- @VisibleForTesting
- public SuggestionData setFormat(String format) {
- mFormat = format;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setText1(String text1) {
- mText1 = text1;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setText2(String text2) {
- mText2 = text2;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setText2Url(String text2Url) {
- mText2Url = text2Url;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIcon1(String icon1) {
- mIcon1 = icon1;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIcon2(String icon2) {
- mIcon2 = icon2;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIntentAction(String intentAction) {
- mIntentAction = intentAction;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIntentData(String intentData) {
- mIntentData = intentData;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIntentExtraData(String intentExtraData) {
- mIntentExtraData = intentExtraData;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setSuggestionQuery(String suggestionQuery) {
- mSuggestionQuery = suggestionQuery;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setShortcutId(String shortcutId) {
- mShortcutId = shortcutId;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setSpinnerWhileRefreshing(boolean spinnerWhileRefreshing) {
- mSpinnerWhileRefreshing = spinnerWhileRefreshing;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setSuggestionLogType(String logType) {
- mLogType = logType;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIsShortcut(boolean isShortcut) {
- mIsShortcut = isShortcut;
- return this;
- }
-
- @VisibleForTesting
- public SuggestionData setIsHistory(boolean isHistory) {
- mIsHistory = isHistory;
- return this;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mFormat == null) ? 0 : mFormat.hashCode());
- result = prime * result + ((mIcon1 == null) ? 0 : mIcon1.hashCode());
- result = prime * result + ((mIcon2 == null) ? 0 : mIcon2.hashCode());
- result = prime * result + ((mIntentAction == null) ? 0 : mIntentAction.hashCode());
- result = prime * result + ((mIntentData == null) ? 0 : mIntentData.hashCode());
- result = prime * result + ((mIntentExtraData == null) ? 0 : mIntentExtraData.hashCode());
- result = prime * result + ((mLogType == null) ? 0 : mLogType.hashCode());
- result = prime * result + ((mShortcutId == null) ? 0 : mShortcutId.hashCode());
- result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
- result = prime * result + (mSpinnerWhileRefreshing ? 1231 : 1237);
- result = prime * result + ((mSuggestionQuery == null) ? 0 : mSuggestionQuery.hashCode());
- result = prime * result + ((mText1 == null) ? 0 : mText1.hashCode());
- result = prime * result + ((mText2 == null) ? 0 : mText2.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- SuggestionData other = (SuggestionData)obj;
- if (mFormat == null) {
- if (other.mFormat != null)
- return false;
- } else if (!mFormat.equals(other.mFormat))
- return false;
- if (mIcon1 == null) {
- if (other.mIcon1 != null)
- return false;
- } else if (!mIcon1.equals(other.mIcon1))
- return false;
- if (mIcon2 == null) {
- if (other.mIcon2 != null)
- return false;
- } else if (!mIcon2.equals(other.mIcon2))
- return false;
- if (mIntentAction == null) {
- if (other.mIntentAction != null)
- return false;
- } else if (!mIntentAction.equals(other.mIntentAction))
- return false;
- if (mIntentData == null) {
- if (other.mIntentData != null)
- return false;
- } else if (!mIntentData.equals(other.mIntentData))
- return false;
- if (mIntentExtraData == null) {
- if (other.mIntentExtraData != null)
- return false;
- } else if (!mIntentExtraData.equals(other.mIntentExtraData))
- return false;
- if (mLogType == null) {
- if (other.mLogType != null)
- return false;
- } else if (!mLogType.equals(other.mLogType))
- return false;
- if (mShortcutId == null) {
- if (other.mShortcutId != null)
- return false;
- } else if (!mShortcutId.equals(other.mShortcutId))
- return false;
- if (mSource == null) {
- if (other.mSource != null)
- return false;
- } else if (!mSource.equals(other.mSource))
- return false;
- if (mSpinnerWhileRefreshing != other.mSpinnerWhileRefreshing)
- return false;
- if (mSuggestionQuery == null) {
- if (other.mSuggestionQuery != null)
- return false;
- } else if (!mSuggestionQuery.equals(other.mSuggestionQuery))
- return false;
- if (mText1 == null) {
- if (other.mText1 != null)
- return false;
- } else if (!mText1.equals(other.mText1))
- return false;
- if (mText2 == null) {
- if (other.mText2 != null)
- return false;
- } else if (!mText2.equals(other.mText2))
- return false;
- return true;
- }
-
- /**
- * Returns a string representation of the contents of this SuggestionData,
- * for debugging purposes.
- */
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder("SuggestionData(");
- appendField(builder, "source", mSource.getName());
- appendField(builder, "text1", mText1);
- appendField(builder, "intentAction", mIntentAction);
- appendField(builder, "intentData", mIntentData);
- appendField(builder, "query", mSuggestionQuery);
- appendField(builder, "shortcutid", mShortcutId);
- appendField(builder, "logtype", mLogType);
- return builder.toString();
- }
-
- private void appendField(StringBuilder builder, String name, String value) {
- if (value != null) {
- builder.append(",").append(name).append("=").append(value);
- }
- }
-
- @VisibleForTesting
- public void setExtras(SuggestionExtras extras) {
- mExtras = extras;
- }
-
- public SuggestionExtras getExtras() {
- return mExtras;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionData.kt b/src/com/android/quicksearchbox/SuggestionData.kt
new file mode 100644
index 0000000..a7bf924
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionData.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.ComponentName
+import android.content.Intent
+import com.google.common.annotations.VisibleForTesting
+
+/**
+ * Holds data for each suggest item including the display data and how to launch the result. Used
+ * for passing from the provider to the suggest cursor.
+ */
+class SuggestionData(override val suggestionSource: Source?) : Suggestion {
+ private var mFormat: String? = null
+ private var mText1: String? = null
+ private var mText2: String? = null
+ private var mText2Url: String? = null
+ private var mIcon1: String? = null
+ private var mIcon2: String? = null
+ private var mShortcutId: String? = null
+ override var isSpinnerWhileRefreshing = false
+ private set
+ private var mIntentAction: String? = null
+ private var mIntentData: String? = null
+ var intentExtraData: String? = null
+ private set
+ private var mSuggestionQuery: String? = null
+ private var mLogType: String? = null
+ override var isSuggestionShortcut = false
+ private set
+ override var isHistorySuggestion = false
+ private set
+ private var mExtras: SuggestionExtras? = null
+ override val suggestionFormat: String
+ get() = mFormat!!
+ override val suggestionText1: String
+ get() = mText1!!
+ override val suggestionText2: String
+ get() = mText2!!
+ override val suggestionText2Url: String
+ get() = mText2Url!!
+ override val suggestionIcon1: String
+ get() = mIcon1!!
+ override val suggestionIcon2: String
+ get() = mIcon2!!
+ override val shortcutId: String
+ get() = mShortcutId!!
+ override val suggestionIntentAction: String?
+ get() = mIntentAction ?: suggestionSource?.defaultIntentAction
+ override val suggestionIntentComponent: ComponentName?
+ get() = suggestionSource?.intentComponent
+ override val suggestionIntentDataString: String
+ get() = mIntentData!!
+ override val suggestionIntentExtraData: String
+ get() = intentExtraData!!
+ override val suggestionQuery: String
+ get() = mSuggestionQuery!!
+ override val suggestionLogType: String
+ get() = mLogType!!
+ override val isWebSearchSuggestion: Boolean
+ get() = Intent.ACTION_WEB_SEARCH.equals(suggestionIntentAction)
+
+ @VisibleForTesting
+ fun setFormat(format: String?): SuggestionData {
+ mFormat = format
+ return this
+ }
+
+ @VisibleForTesting
+ fun setText1(text1: String?): SuggestionData {
+ mText1 = text1
+ return this
+ }
+
+ @VisibleForTesting
+ fun setText2(text2: String?): SuggestionData {
+ mText2 = text2
+ return this
+ }
+
+ @VisibleForTesting
+ fun setText2Url(text2Url: String?): SuggestionData {
+ mText2Url = text2Url
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIcon1(icon1: String?): SuggestionData {
+ mIcon1 = icon1
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIcon2(icon2: String?): SuggestionData {
+ mIcon2 = icon2
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIntentAction(intentAction: String?): SuggestionData {
+ mIntentAction = intentAction
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIntentData(intentData: String?): SuggestionData {
+ mIntentData = intentData
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIntentExtraData(intentExtraData: String?): SuggestionData {
+ this.intentExtraData = intentExtraData
+ return this
+ }
+
+ @VisibleForTesting
+ fun setSuggestionQuery(suggestionQuery: String?): SuggestionData {
+ mSuggestionQuery = suggestionQuery
+ return this
+ }
+
+ @VisibleForTesting
+ fun setShortcutId(shortcutId: String?): SuggestionData {
+ mShortcutId = shortcutId
+ return this
+ }
+
+ @VisibleForTesting
+ fun setSpinnerWhileRefreshing(spinnerWhileRefreshing: Boolean): SuggestionData {
+ isSpinnerWhileRefreshing = spinnerWhileRefreshing
+ return this
+ }
+
+ @VisibleForTesting
+ fun setSuggestionLogType(logType: String?): SuggestionData {
+ mLogType = logType
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIsShortcut(isShortcut: Boolean): SuggestionData {
+ isSuggestionShortcut = isShortcut
+ return this
+ }
+
+ @VisibleForTesting
+ fun setIsHistory(isHistory: Boolean): SuggestionData {
+ isHistorySuggestion = isHistory
+ return this
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ val prime = 31
+ var result = 1
+ result = prime * result + if (mFormat == null) 0 else mFormat.hashCode()
+ result = prime * result + if (mIcon1 == null) 0 else mIcon1.hashCode()
+ result = prime * result + if (mIcon2 == null) 0 else mIcon2.hashCode()
+ result = prime * result + if (mIntentAction == null) 0 else mIntentAction.hashCode()
+ result = prime * result + if (mIntentData == null) 0 else mIntentData.hashCode()
+ result = prime * result + if (intentExtraData == null) 0 else intentExtraData.hashCode()
+ result = prime * result + if (mLogType == null) 0 else mLogType.hashCode()
+ result = prime * result + if (mShortcutId == null) 0 else mShortcutId.hashCode()
+ result = prime * result + if (suggestionSource == null) 0 else suggestionSource.hashCode()
+ result = prime * result + if (isSpinnerWhileRefreshing) 1231 else 1237
+ result = prime * result + if (mSuggestionQuery == null) 0 else mSuggestionQuery.hashCode()
+ result = prime * result + if (mText1 == null) 0 else mText1.hashCode()
+ result = prime * result + if (mText2 == null) 0 else mText2.hashCode()
+ return result
+ }
+
+ @Override
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (this::class !== other::class) return false
+ val suggestionData = other as SuggestionData
+ if (mFormat == null) {
+ if (suggestionData.mFormat != null) return false
+ } else if (!mFormat.equals(suggestionData.mFormat)) return false
+ if (mIcon1 == null) {
+ if (suggestionData.mIcon1 != null) return false
+ } else if (!mIcon1.equals(suggestionData.mIcon1)) return false
+ if (mIcon2 == null) {
+ if (suggestionData.mIcon2 != null) return false
+ } else if (!mIcon2.equals(suggestionData.mIcon2)) return false
+ if (mIntentAction == null) {
+ if (suggestionData.mIntentAction != null) return false
+ } else if (!mIntentAction.equals(suggestionData.mIntentAction)) return false
+ if (mIntentData == null) {
+ if (suggestionData.mIntentData != null) return false
+ } else if (!mIntentData.equals(suggestionData.mIntentData)) return false
+ if (intentExtraData == null) {
+ if (suggestionData.intentExtraData != null) return false
+ } else if (!intentExtraData.equals(suggestionData.intentExtraData)) return false
+ if (mLogType == null) {
+ if (suggestionData.mLogType != null) return false
+ } else if (!mLogType.equals(suggestionData.mLogType)) return false
+ if (mShortcutId == null) {
+ if (suggestionData.mShortcutId != null) return false
+ } else if (!mShortcutId.equals(suggestionData.mShortcutId)) return false
+ if (suggestionSource == null) {
+ if (suggestionData.suggestionSource != null) return false
+ } else if (!suggestionSource.equals(suggestionData.suggestionSource)) return false
+ if (isSpinnerWhileRefreshing != suggestionData.isSpinnerWhileRefreshing) return false
+ if (mSuggestionQuery == null) {
+ if (suggestionData.mSuggestionQuery != null) return false
+ } else if (!mSuggestionQuery.equals(suggestionData.mSuggestionQuery)) return false
+ if (mText1 == null) {
+ if (suggestionData.mText1 != null) return false
+ } else if (!mText1.equals(suggestionData.mText1)) return false
+ if (mText2 == null) {
+ if (suggestionData.mText2 != null) return false
+ } else if (!mText2.equals(suggestionData.mText2)) return false
+ return true
+ }
+
+ /**
+ * Returns a string representation of the contents of this SuggestionData, for debugging purposes.
+ */
+ @Override
+ override fun toString(): String {
+ val builder: StringBuilder = StringBuilder("SuggestionData(")
+ appendField(builder, "source", suggestionSource!!.name)
+ appendField(builder, "text1", mText1)
+ appendField(builder, "intentAction", mIntentAction)
+ appendField(builder, "intentData", mIntentData)
+ appendField(builder, "query", mSuggestionQuery)
+ appendField(builder, "shortcutid", mShortcutId)
+ appendField(builder, "logtype", mLogType)
+ return builder.toString()
+ }
+
+ private fun appendField(builder: StringBuilder, name: String, value: String?) {
+ if (value != null) {
+ builder.append(",").append(name).append("=").append(value)
+ }
+ }
+
+ @set:VisibleForTesting
+ override var extras: SuggestionExtras?
+ get() = mExtras
+ set(extras) {
+ mExtras = extras
+ }
+}
diff --git a/src/com/android/quicksearchbox/SuggestionExtras.java b/src/com/android/quicksearchbox/SuggestionExtras.java
deleted file mode 100644
index 263c808..0000000
--- a/src/com/android/quicksearchbox/SuggestionExtras.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import org.json.JSONException;
-
-import java.util.Collection;
-
-/**
- * Extra data that can be attached to a suggestion.
- */
-public interface SuggestionExtras {
-
- /**
- * Return the names of custom columns present in these extras.
- */
- Collection<String> getExtraColumnNames();
-
- /**
- * @param columnName The column to get a value from.
- */
- String getExtra(String columnName);
-
- /**
- * Flatten these extras as a JSON object.
- */
- String toJsonString() throws JSONException;
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionNonFormatter.java b/src/com/android/quicksearchbox/SuggestionExtras.kt
index d7dc0bd..583b7b5 100644
--- a/src/com/android/quicksearchbox/SuggestionNonFormatter.java
+++ b/src/com/android/quicksearchbox/SuggestionExtras.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,22 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.quicksearchbox
-package com.android.quicksearchbox;
+import org.json.JSONException
+/** Extra data that can be attached to a suggestion. */
+interface SuggestionExtras {
+ /** Return the names of custom columns present in these extras. */
+ val extraColumnNames: Collection<String>
-/**
- * Basic SuggestionFormatter that does no formatting.
- */
-public class SuggestionNonFormatter extends SuggestionFormatter {
-
- public SuggestionNonFormatter(TextAppearanceFactory spanFactory) {
- super(spanFactory);
- }
-
- @Override
- public CharSequence formatSuggestion(String query, String suggestion) {
- return suggestion;
- }
+ /** @param columnName The column to get a value from. */
+ fun getExtra(columnName: String?): String?
+ /** Flatten these extras as a JSON object. */
+ @Throws(JSONException::class) fun toJsonString(): String?
}
diff --git a/src/com/android/quicksearchbox/SuggestionFilter.java b/src/com/android/quicksearchbox/SuggestionFilter.java
deleted file mode 100644
index aaf0c70..0000000
--- a/src/com/android/quicksearchbox/SuggestionFilter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-/**
- * Interface for choosing which suggestions to include in a promoted list.
- */
-public interface SuggestionFilter {
- /**
- * Determines if a suggestion should be added to the promoted suggestion list.
- *
- * @param s The suggestion in question
- * @return true to include it in the results
- */
- boolean accept(Suggestion s);
-}
diff --git a/src/com/android/quicksearchbox/SuggestionFilter.kt b/src/com/android/quicksearchbox/SuggestionFilter.kt
new file mode 100644
index 0000000..5cfb5bd
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionFilter.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** Interface for choosing which suggestions to include in a promoted list. */
+interface SuggestionFilter {
+ /**
+ * Determines if a suggestion should be added to the promoted suggestion list.
+ *
+ * @param s The suggestion in question
+ * @return true to include it in the results
+ */
+ fun accept(s: Suggestion?): Boolean
+}
diff --git a/src/com/android/quicksearchbox/SuggestionFormatter.java b/src/com/android/quicksearchbox/SuggestionFormatter.java
deleted file mode 100644
index a6eab9a..0000000
--- a/src/com/android/quicksearchbox/SuggestionFormatter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.text.Spannable;
-
-/**
- * Suggestion formatter interface. This is used to bold (or otherwise highlight) portions of a
- * suggestion which were not a part of the query.
- */
-public abstract class SuggestionFormatter {
-
- private final TextAppearanceFactory mSpanFactory;
-
- protected SuggestionFormatter(TextAppearanceFactory spanFactory) {
- mSpanFactory = spanFactory;
- }
-
- /**
- * Formats a suggestion for display in the UI.
- *
- * @param query the query as entered by the user
- * @param suggestion the suggestion
- * @return Formatted suggestion text.
- */
- public abstract CharSequence formatSuggestion(String query, String suggestion);
-
- protected void applyQueryTextStyle(Spannable text, int start, int end) {
- if (start == end) return;
- setSpans(text, start, end, mSpanFactory.createSuggestionQueryTextAppearance());
- }
-
- protected void applySuggestedTextStyle(Spannable text, int start, int end) {
- if (start == end) return;
- setSpans(text, start, end, mSpanFactory.createSuggestionSuggestedTextAppearance());
- }
-
- private void setSpans(Spannable text, int start, int end, Object[] spans) {
- for (Object span : spans) {
- text.setSpan(span, start, end, 0);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionFormatter.kt b/src/com/android/quicksearchbox/SuggestionFormatter.kt
new file mode 100644
index 0000000..fea3ee9
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionFormatter.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.text.Spannable
+
+/**
+ * Suggestion formatter interface. This is used to bold (or otherwise highlight) portions of a
+ * suggestion which were not a part of the query.
+ */
+abstract class SuggestionFormatter
+protected constructor(private val mSpanFactory: TextAppearanceFactory) {
+ /**
+ * Formats a suggestion for display in the UI.
+ *
+ * @param query the query as entered by the user
+ * @param suggestion the suggestion
+ * @return Formatted suggestion text.
+ */
+ abstract fun formatSuggestion(query: String?, suggestion: String?): CharSequence?
+ protected fun applyQueryTextStyle(text: Spannable, start: Int, end: Int) {
+ if (start == end) return
+ setSpans(text, start, end, mSpanFactory.createSuggestionQueryTextAppearance())
+ }
+
+ protected fun applySuggestedTextStyle(text: Spannable, start: Int, end: Int) {
+ if (start == end) return
+ setSpans(text, start, end, mSpanFactory.createSuggestionSuggestedTextAppearance())
+ }
+
+ private fun setSpans(text: Spannable, start: Int, end: Int, spans: Array<Any>) {
+ for (span in spans) {
+ text.setSpan(span, start, end, 0)
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ResultFilter.java b/src/com/android/quicksearchbox/SuggestionNonFormatter.kt
index 76d88b6..d473d74 100644
--- a/src/com/android/quicksearchbox/ResultFilter.java
+++ b/src/com/android/quicksearchbox/SuggestionNonFormatter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,18 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quicksearchbox;
-
-/**
- * {@link SuggestionFilter} that accepts only results (not web suggestions).
- */
-public class ResultFilter implements SuggestionFilter {
-
- public ResultFilter() {
- }
-
- public boolean accept(Suggestion s) {
- return !s.isWebSearchSuggestion();
- }
+package com.android.quicksearchbox
+/** Basic SuggestionFormatter that does no formatting. */
+class SuggestionNonFormatter(spanFactory: TextAppearanceFactory?) :
+ SuggestionFormatter(spanFactory!!) {
+ @Override
+ override fun formatSuggestion(query: String?, suggestion: String?): CharSequence? {
+ return suggestion
+ }
}
diff --git a/src/com/android/quicksearchbox/SuggestionPosition.java b/src/com/android/quicksearchbox/SuggestionPosition.java
deleted file mode 100644
index 8311978..0000000
--- a/src/com/android/quicksearchbox/SuggestionPosition.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-
-/**
- * A pointer to a suggestion in a {@link SuggestionCursor}.
- *
- */
-public class SuggestionPosition extends AbstractSuggestionWrapper {
-
- private final SuggestionCursor mCursor;
-
- private final int mPosition;
-
- public SuggestionPosition(SuggestionCursor cursor) {
- this(cursor, cursor.getPosition());
- }
-
- public SuggestionPosition(SuggestionCursor cursor, int suggestionPos) {
- mCursor = cursor;
- mPosition = suggestionPos;
- }
-
- public SuggestionCursor getCursor() {
- return mCursor;
- }
-
- /**
- * Gets the suggestion cursor, moved to point to the right suggestion.
- */
- @Override
- protected Suggestion current() {
- mCursor.moveTo(mPosition);
- return mCursor;
- }
-
- public int getPosition() {
- return mPosition;
- }
-
- @Override
- public String toString() {
- return mCursor + ":" + mPosition;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionPosition.kt b/src/com/android/quicksearchbox/SuggestionPosition.kt
new file mode 100644
index 0000000..36be904
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionPosition.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** A pointer to a suggestion in a [SuggestionCursor]. */
+class SuggestionPosition
+@JvmOverloads
+constructor(val cursor: SuggestionCursor?, val position: Int = cursor!!.position) :
+ AbstractSuggestionWrapper() {
+
+ /** Gets the suggestion cursor, moved to point to the right suggestion. */
+ @Override
+ override fun current(): Suggestion? {
+ cursor?.moveTo(position)
+ return cursor
+ }
+
+ @Override
+ override fun toString(): String {
+ return cursor.toString() + ":" + position
+ }
+}
diff --git a/src/com/android/quicksearchbox/SuggestionUtils.java b/src/com/android/quicksearchbox/SuggestionUtils.java
deleted file mode 100644
index bf15053..0000000
--- a/src/com/android/quicksearchbox/SuggestionUtils.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.app.SearchManager;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * Some utilities for suggestions.
- */
-public class SuggestionUtils {
-
- private SuggestionUtils() {
- }
-
- public static Intent getSuggestionIntent(SuggestionCursor suggestion, Bundle appSearchData) {
- String action = suggestion.getSuggestionIntentAction();
-
- String data = suggestion.getSuggestionIntentDataString();
- String query = suggestion.getSuggestionQuery();
- String userQuery = suggestion.getUserQuery();
- String extraData = suggestion.getSuggestionIntentExtraData();
-
- // Now build the Intent
- Intent intent = new Intent(action);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want.
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- if (data != null) {
- intent.setData(Uri.parse(data));
- }
- intent.putExtra(SearchManager.USER_QUERY, userQuery);
- if (query != null) {
- intent.putExtra(SearchManager.QUERY, query);
- }
- if (extraData != null) {
- intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
- }
- if (appSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, appSearchData);
- }
-
- intent.setComponent(suggestion.getSuggestionIntentComponent());
- return intent;
- }
-
- /**
- * Gets a unique key that identifies a suggestion. This is used to avoid
- * duplicate suggestions.
- */
- public static String getSuggestionKey(Suggestion suggestion) {
- String action = makeKeyComponent(suggestion.getSuggestionIntentAction());
- String data = makeKeyComponent(normalizeUrl(suggestion.getSuggestionIntentDataString()));
- String query = makeKeyComponent(normalizeUrl(suggestion.getSuggestionQuery()));
- // calculating accurate size of string builder avoids an allocation vs starting with
- // the default size and having to expand.
- int size = action.length() + 2 + data.length() + query.length();
- return new StringBuilder(size)
- .append(action)
- .append('#')
- .append(data)
- .append('#')
- .append(query)
- .toString();
- }
-
- private static String makeKeyComponent(String str) {
- return str == null ? "" : str;
- }
-
- private static final String SCHEME_SEPARATOR = "://";
- private static final String DEFAULT_SCHEME = "http";
-
- /**
- * Simple url normalization that adds http:// if no scheme exists, and
- * strips empty paths, e.g.,
- * www.google.com/ -> http://www.google.com. Used to prevent obvious
- * duplication of nav suggestions, bookmarks and urls entered by the user.
- */
- @VisibleForTesting
- static String normalizeUrl(String url) {
- String normalized;
- if (url != null) {
- int start;
- int schemePos = url.indexOf(SCHEME_SEPARATOR);
- if (schemePos == -1) {
- // no scheme - add the default
- normalized = DEFAULT_SCHEME + SCHEME_SEPARATOR + url;
- start = DEFAULT_SCHEME.length() + SCHEME_SEPARATOR.length();
- } else {
- normalized = url;
- start = schemePos + SCHEME_SEPARATOR.length();
- }
- int end = normalized.length();
- if (normalized.indexOf('/', start) == end - 1) {
- end--;
- }
- return normalized.substring(0, end);
- }
- return url;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SuggestionUtils.kt b/src/com/android/quicksearchbox/SuggestionUtils.kt
new file mode 100644
index 0000000..cde4cd6
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionUtils.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import com.google.common.annotations.VisibleForTesting
+import kotlin.text.StringBuilder
+
+/** Some utilities for suggestions. */
+object SuggestionUtils {
+ @JvmStatic
+ fun getSuggestionIntent(suggestion: SuggestionCursor?, appSearchData: Bundle?): Intent {
+ val action: String? = suggestion?.suggestionIntentAction
+ val data: String? = suggestion?.suggestionIntentDataString
+ val query: String? = suggestion?.suggestionQuery
+ val userQuery: String? = suggestion?.userQuery
+ val extraData: String? = suggestion?.suggestionIntentExtraData
+
+ // Now build the Intent
+ val intent = Intent(action)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // We need CLEAR_TOP to avoid reusing an old task that has other activities
+ // on top of the one we want.
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ if (data != null) {
+ intent.setData(Uri.parse(data))
+ }
+ intent.putExtra(SearchManager.USER_QUERY, userQuery)
+ if (query != null) {
+ intent.putExtra(SearchManager.QUERY, query)
+ }
+ if (extraData != null) {
+ intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData)
+ }
+ if (appSearchData != null) {
+ intent.putExtra(SearchManager.APP_DATA, appSearchData)
+ }
+ intent.setComponent(suggestion?.suggestionIntentComponent)
+ return intent
+ }
+
+ /**
+ * Gets a unique key that identifies a suggestion. This is used to avoid duplicate suggestions.
+ */
+ @JvmStatic
+ fun getSuggestionKey(suggestion: Suggestion): String {
+ val action: String = makeKeyComponent(suggestion.suggestionIntentAction)
+ val data: String = makeKeyComponent(normalizeUrl(suggestion.suggestionIntentDataString))
+ val query: String = makeKeyComponent(normalizeUrl(suggestion.suggestionQuery))
+ // calculating accurate size of string builder avoids an allocation vs starting with
+ // the default size and having to expand.
+ val size: Int = action.length + 2 + data.length + query.length
+ return StringBuilder(size)
+ .append(action)
+ .append('#')
+ .append(data)
+ .append('#')
+ .append(query)
+ .toString()
+ }
+
+ private fun makeKeyComponent(str: String?): String {
+ return str ?: ""
+ }
+
+ private const val SCHEME_SEPARATOR = "://"
+ private const val DEFAULT_SCHEME = "http"
+
+ /**
+ * Simple url normalization that adds http:// if no scheme exists, and strips empty paths, e.g.,
+ * www.google.com/ -> http://www.google.com. Used to prevent obvious duplication of nav
+ * suggestions, bookmarks and urls entered by the user.
+ */
+ @JvmStatic
+ @VisibleForTesting
+ fun normalizeUrl(url: String?): String? {
+ val normalized: String
+ if (url != null) {
+ val start: Int
+ val schemePos: Int = url.indexOf(SCHEME_SEPARATOR)
+ if (schemePos == -1) {
+ // no scheme - add the default
+ normalized = DEFAULT_SCHEME + SCHEME_SEPARATOR + url
+ start = DEFAULT_SCHEME.length + SCHEME_SEPARATOR.length
+ } else {
+ normalized = url
+ start = schemePos + SCHEME_SEPARATOR.length
+ }
+ var end: Int = normalized.length
+ if (normalized.indexOf('/', start) == end - 1) {
+ end--
+ }
+ return normalized.substring(0, end)
+ }
+ return url
+ }
+}
diff --git a/src/com/android/quicksearchbox/Suggestions.java b/src/com/android/quicksearchbox/Suggestions.java
deleted file mode 100644
index aca2a67..0000000
--- a/src/com/android/quicksearchbox/Suggestions.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.database.DataSetObservable;
-import android.database.DataSetObserver;
-import android.util.Log;
-
-/**
- * Collects all corpus results for a single query.
- */
-public class Suggestions {
- private static final boolean DBG = false;
- private static final String TAG = "QSB.Suggestions";
-
- /** True if {@link Suggestions#close} has been called. */
- private boolean mClosed = false;
- protected final String mQuery;
-
- /**
- * The observers that want notifications of changes to the published suggestions.
- * This object may be accessed on any thread.
- */
- private final DataSetObservable mDataSetObservable = new DataSetObservable();
-
- private Source mSource;
-
- private SourceResult mResult;
-
- private int mRefCount = 0;
-
- private boolean mDone = false;
-
- public Suggestions(String query, Source source) {
- mQuery = query;
- mSource = source;
- }
-
- public void acquire() {
- mRefCount++;
- }
-
- public void release() {
- mRefCount--;
- if (mRefCount <= 0) {
- close();
- }
- }
-
- public Source getSource() {
- return mSource;
- }
-
- /**
- * Marks the suggestions set as complete, regardless of whether all corpora have
- * returned.
- */
- public void done() {
- mDone = true;
- }
-
- /**
- * Checks whether all sources have reported.
- * Must be called on the UI thread, or before this object is seen by the UI thread.
- */
- public boolean isDone() {
- return mDone || mResult != null;
- }
-
- /**
- * Adds a list of corpus results. Must be called on the UI thread, or before this
- * object is seen by the UI thread.
- */
- public void addResults(SourceResult result) {
- if (isClosed()) {
- result.close();
- return;
- }
-
- if (DBG) {
- Log.d(TAG, "addResults["+ hashCode() + "] source:" +
- result.getSource().getName() + " results:" + result.getCount());
- }
- if (!mQuery.equals(result.getUserQuery())) {
- throw new IllegalArgumentException("Got result for wrong query: "
- + mQuery + " != " + result.getUserQuery());
- }
- mResult = result;
- notifyDataSetChanged();
- }
-
- /**
- * Registers an observer that will be notified when the reported results or
- * the done status changes.
- */
- public void registerDataSetObserver(DataSetObserver observer) {
- if (mClosed) {
- throw new IllegalStateException("registerDataSetObserver() when closed");
- }
- mDataSetObservable.registerObserver(observer);
- }
-
-
- /**
- * Unregisters an observer.
- */
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.unregisterObserver(observer);
- }
-
- /**
- * Calls {@link DataSetObserver#onChanged()} on all observers.
- */
- protected void notifyDataSetChanged() {
- if (DBG) Log.d(TAG, "notifyDataSetChanged()");
- mDataSetObservable.notifyChanged();
- }
-
- /**
- * Closes all the source results and unregisters all observers.
- */
- private void close() {
- if (DBG) Log.d(TAG, "close() [" + hashCode() + "]");
- if (mClosed) {
- throw new IllegalStateException("Double close()");
- }
- mClosed = true;
- mDataSetObservable.unregisterAll();
- if (mResult != null) {
- mResult.close();
- }
- mResult = null;
- }
-
- public boolean isClosed() {
- return mClosed;
- }
-
- @Override
- protected void finalize() {
- if (!mClosed) {
- Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[" + getQuery() + "]");
- }
- }
-
- public String getQuery() {
- return mQuery;
- }
-
- /**
- * Gets the list of corpus results reported so far. Do not modify or hang on to
- * the returned iterator.
- */
- public SourceResult getResult() {
- return mResult;
- }
-
- public SourceResult getWebResult() {
- return mResult;
- }
-
- /**
- * Gets the number of source results.
- * Must be called on the UI thread, or before this object is seen by the UI thread.
- */
- public int getResultCount() {
- if (isClosed()) {
- throw new IllegalStateException("Called getSourceCount() when closed.");
- }
- return mResult == null ? 0 : mResult.getCount();
- }
-
- @Override
- public String toString() {
- return "Suggestions@" + hashCode() + "{source=" + mSource
- + ",getResultCount()=" + getResultCount() + "}";
- }
-
-}
diff --git a/src/com/android/quicksearchbox/Suggestions.kt b/src/com/android/quicksearchbox/Suggestions.kt
new file mode 100644
index 0000000..2b4b620
--- /dev/null
+++ b/src/com/android/quicksearchbox/Suggestions.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.database.DataSetObservable
+import android.database.DataSetObserver
+import android.util.Log
+
+/** Collects all corpus results for a single query. */
+class Suggestions(val query: String, val source: Source) {
+
+ /**
+ * The observers that want notifications of changes to the published suggestions. This object may
+ * be accessed on any thread.
+ */
+ private val mDataSetObservable: DataSetObservable = DataSetObservable()
+
+ private var mResult: SourceResult? = null
+
+ private var mRefCount = 0
+
+ private var mDone = false
+
+ /** True if [Suggestions.close] has been called. */
+ var isClosed = false
+ private set
+
+ /**
+ * Gets the list of corpus results reported so far. Do not modify or hang on to the returned
+ * iterator.
+ */
+ fun getResult(): SourceResult? {
+ return mResult
+ }
+
+ fun getWebResult(): SourceResult? {
+ return mResult
+ }
+
+ fun acquire() {
+ mRefCount++
+ }
+
+ fun release() {
+ mRefCount--
+ if (mRefCount <= 0) {
+ close()
+ }
+ }
+
+ /** Marks the suggestions set as complete, regardless of whether all corpora have returned. */
+ fun done() {
+ mDone = true
+ }
+
+ /**
+ * Checks whether all sources have reported. Must be called on the UI thread, or before this
+ * object is seen by the UI thread.
+ */
+ val isDone: Boolean
+ get() = mDone || mResult != null
+
+ /**
+ * Adds a list of corpus results. Must be called on the UI thread, or before this object is seen
+ * by the UI thread.
+ */
+ fun addResults(result: SourceResult?) {
+ if (isClosed) {
+ result?.close()
+ return
+ }
+ if (DBG) {
+ Log.d(
+ TAG,
+ "addResults[" +
+ hashCode().toString() +
+ "] source:" +
+ result?.source?.name.toString() +
+ " results:" +
+ result?.count
+ )
+ }
+ if (query != result?.userQuery) {
+ throw IllegalArgumentException(
+ "Got result for wrong query: " + query + " != " + result?.userQuery
+ )
+ }
+ mResult = result
+ notifyDataSetChanged()
+ }
+
+ /**
+ * Registers an observer that will be notified when the reported results or the done status
+ * changes.
+ */
+ fun registerDataSetObserver(observer: DataSetObserver?) {
+ if (isClosed) {
+ throw IllegalStateException("registerDataSetObserver() when closed")
+ }
+ mDataSetObservable.registerObserver(observer)
+ }
+
+ /** Unregisters an observer. */
+ fun unregisterDataSetObserver(observer: DataSetObserver?) {
+ mDataSetObservable.unregisterObserver(observer)
+ }
+
+ /** Calls [DataSetObserver.onChanged] on all observers. */
+ protected fun notifyDataSetChanged() {
+ if (DBG) Log.d(TAG, "notifyDataSetChanged()")
+ mDataSetObservable.notifyChanged()
+ }
+
+ /** Closes all the source results and unregisters all observers. */
+ private fun close() {
+ if (DBG) Log.d(TAG, "close() [" + hashCode().toString() + "]")
+ if (isClosed) {
+ throw IllegalStateException("Double close()")
+ }
+ isClosed = true
+ mDataSetObservable.unregisterAll()
+ mResult?.close()
+ mResult = null
+ }
+
+ @Override
+ protected fun finalize() {
+ if (!isClosed) {
+ Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[$query]")
+ }
+ }
+
+ /**
+ * Gets the number of source results. Must be called on the UI thread, or before this object is
+ * seen by the UI thread.
+ */
+ val resultCount: Int
+ get() {
+ if (isClosed) {
+ throw IllegalStateException("Called resultCount when closed.")
+ }
+ return mResult?.count ?: 0
+ }
+
+ @Override
+ override fun toString(): String {
+ return "Suggestions@" +
+ hashCode().toString() +
+ "{source=" +
+ source.toString() +
+ ",resultCount=" +
+ resultCount.toString() +
+ "}"
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.Suggestions"
+ }
+}
diff --git a/src/com/android/quicksearchbox/SuggestionsProvider.java b/src/com/android/quicksearchbox/SuggestionsProvider.java
deleted file mode 100644
index 9196018..0000000
--- a/src/com/android/quicksearchbox/SuggestionsProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-/**
- * Provides a set of suggestion results for a query..
- *
- */
-public interface SuggestionsProvider {
-
- /**
- * Gets suggestions for a query.
- *
- * @param query The query.
- * @param source The source to query. Must be non-null.
- */
- Suggestions getSuggestions(String query, Source source);
-
- void close();
-}
diff --git a/src/com/android/quicksearchbox/SuggestionsProvider.kt b/src/com/android/quicksearchbox/SuggestionsProvider.kt
new file mode 100644
index 0000000..aaa7e21
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionsProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+/** Provides a set of suggestion results for a query.. */
+interface SuggestionsProvider {
+ /**
+ * Gets suggestions for a query.
+ *
+ * @param query The query.
+ * @param source The source to query. Must be non-null.
+ */
+ fun getSuggestions(query: String, source: Source): Suggestions
+ fun close()
+}
diff --git a/src/com/android/quicksearchbox/SuggestionsProviderImpl.java b/src/com/android/quicksearchbox/SuggestionsProviderImpl.java
deleted file mode 100644
index 76a9071..0000000
--- a/src/com/android/quicksearchbox/SuggestionsProviderImpl.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.os.Handler;
-import android.util.Log;
-
-import com.android.quicksearchbox.util.BatchingNamedTaskExecutor;
-import com.android.quicksearchbox.util.Consumer;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-import com.android.quicksearchbox.util.NoOpConsumer;
-
-/**
- * Suggestions provider implementation.
- *
- * The provider will only handle a single query at a time. If a new query comes
- * in, the old one is cancelled.
- */
-public class SuggestionsProviderImpl implements SuggestionsProvider {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SuggestionsProviderImpl";
-
- private final Config mConfig;
-
- private final NamedTaskExecutor mQueryExecutor;
-
- private final Handler mPublishThread;
-
- private final Logger mLogger;
-
- public SuggestionsProviderImpl(Config config,
- NamedTaskExecutor queryExecutor,
- Handler publishThread,
- Logger logger) {
- mConfig = config;
- mQueryExecutor = queryExecutor;
- mPublishThread = publishThread;
- mLogger = logger;
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public Suggestions getSuggestions(String query, Source sourceToQuery) {
- if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
- final Suggestions suggestions = new Suggestions(query, sourceToQuery);
- Log.i(TAG, "chars:" + query.length() + ",source:" + sourceToQuery);
-
- Consumer<SourceResult> receiver;
- if (shouldDisplayResults(query)) {
- receiver = new SuggestionCursorReceiver(suggestions);
- } else {
- receiver = new NoOpConsumer<SourceResult>();
- suggestions.done();
- }
-
- int maxResults = mConfig.getMaxResultsPerSource();
- QueryTask.startQuery(query, maxResults, sourceToQuery, mQueryExecutor,
- mPublishThread, receiver);
-
- return suggestions;
- }
-
- private boolean shouldDisplayResults(String query) {
- if (query.length() == 0 && !mConfig.showSuggestionsForZeroQuery()) {
- // Note that even though we don't display such results, it's
- // useful to run the query itself because it warms up the network
- // connection.
- return false;
- }
- return true;
- }
-
-
- private class SuggestionCursorReceiver implements Consumer<SourceResult> {
- private final Suggestions mSuggestions;
-
- public SuggestionCursorReceiver(Suggestions suggestions) {
- mSuggestions = suggestions;
- }
-
- @Override
- public boolean consume(SourceResult cursor) {
- if (DBG) {
- Log.d(TAG, "SuggestionCursorReceiver.consume(" + cursor + ") corpus=" +
- cursor.getSource() + " count = " + cursor.getCount());
- }
- // publish immediately
- if (DBG) Log.d(TAG, "Publishing results");
- mSuggestions.addResults(cursor);
- if (cursor != null && mLogger != null) {
- mLogger.logLatency(cursor);
- }
- return true;
- }
-
- }
-}
diff --git a/src/com/android/quicksearchbox/SuggestionsProviderImpl.kt b/src/com/android/quicksearchbox/SuggestionsProviderImpl.kt
new file mode 100644
index 0000000..9c02c07
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionsProviderImpl.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.os.Handler
+import android.util.Log
+import com.android.quicksearchbox.util.Consumer
+import com.android.quicksearchbox.util.NamedTaskExecutor
+import com.android.quicksearchbox.util.NoOpConsumer
+
+/**
+ * Suggestions provider implementation.
+ *
+ * The provider will only handle a single query at a time. If a new query comes in, the old one is
+ * cancelled.
+ */
+class SuggestionsProviderImpl(
+ private val mConfig: Config,
+ private val mQueryExecutor: NamedTaskExecutor,
+ publishThread: Handler?,
+ logger: Logger?
+) : SuggestionsProvider {
+
+ private val mPublishThread: Handler?
+
+ private val mLogger: Logger?
+
+ @Override override fun close() {}
+
+ @Override
+ override fun getSuggestions(query: String, source: Source): Suggestions {
+ if (DBG) Log.d(TAG, "getSuggestions($query)")
+ val suggestions = Suggestions(query, source)
+ Log.i(TAG, "chars:" + query.length.toString() + ",source:" + source)
+ val receiver: Consumer<SourceResult?>
+ if (shouldDisplayResults(query)) {
+ receiver = SuggestionCursorReceiver(suggestions)
+ } else {
+ receiver = NoOpConsumer()
+ suggestions.done()
+ }
+ val maxResults: Int = mConfig.maxResultsPerSource
+ QueryTask.startQuery(query, maxResults, source, mQueryExecutor, mPublishThread, receiver)
+ return suggestions
+ }
+
+ private fun shouldDisplayResults(query: String): Boolean {
+ return !(query.isEmpty() && !mConfig.showSuggestionsForZeroQuery())
+ }
+
+ private inner class SuggestionCursorReceiver(private val mSuggestions: Suggestions) :
+ Consumer<SourceResult?> {
+ @Override
+ override fun consume(value: SourceResult?): Boolean {
+ if (DBG) {
+ Log.d(
+ TAG,
+ "SuggestionCursorReceiver.consume(" +
+ value +
+ ") corpus=" +
+ value?.source +
+ " count = " +
+ value?.count
+ )
+ }
+ // publish immediately
+ if (DBG) Log.d(TAG, "Publishing results")
+ mSuggestions.addResults(value)
+ if (value != null && mLogger != null) {
+ mLogger.logLatency(value)
+ }
+ return true
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SuggestionsProviderImpl"
+ }
+
+ init {
+ mPublishThread = publishThread
+ mLogger = logger
+ }
+}
diff --git a/src/com/android/quicksearchbox/TextAppearanceFactory.java b/src/com/android/quicksearchbox/TextAppearanceFactory.java
deleted file mode 100644
index af950d9..0000000
--- a/src/com/android/quicksearchbox/TextAppearanceFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox;
-
-import android.content.Context;
-import android.text.style.TextAppearanceSpan;
-
-/**
- * Factory class for text appearances.
- */
-public class TextAppearanceFactory {
- private final Context mContext;
-
- public TextAppearanceFactory(Context context) {
- mContext = context;
- }
-
- public Object[] createSuggestionQueryTextAppearance() {
- return new Object[]{
- new TextAppearanceSpan(mContext, R.style.SuggestionText1_Query)
- };
- }
-
- public Object[] createSuggestionSuggestedTextAppearance() {
- return new Object[]{
- new TextAppearanceSpan(mContext, R.style.SuggestionText1_Suggested)
- };
- }
-
-}
diff --git a/src/com/android/quicksearchbox/TextAppearanceFactory.kt b/src/com/android/quicksearchbox/TextAppearanceFactory.kt
new file mode 100644
index 0000000..0b1e0cc
--- /dev/null
+++ b/src/com/android/quicksearchbox/TextAppearanceFactory.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.content.Context
+import android.text.style.TextAppearanceSpan
+
+/** Factory class for text appearances. */
+open class TextAppearanceFactory(context: Context?) {
+ private val mContext: Context?
+ open fun createSuggestionQueryTextAppearance(): Array<Any> {
+ return arrayOf(TextAppearanceSpan(mContext, R.style.SuggestionText1_Query))
+ }
+
+ open fun createSuggestionSuggestedTextAppearance(): Array<Any> {
+ return arrayOf(TextAppearanceSpan(mContext, R.style.SuggestionText1_Suggested))
+ }
+
+ init {
+ mContext = context
+ }
+}
diff --git a/src/com/android/quicksearchbox/VoiceSearch.java b/src/com/android/quicksearchbox/VoiceSearch.java
deleted file mode 100644
index 674db96..0000000
--- a/src/com/android/quicksearchbox/VoiceSearch.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.app.SearchManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ComponentInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.speech.RecognizerIntent;
-import android.util.Log;
-
-/**
- * Voice Search integration.
- */
-public class VoiceSearch {
-
- private static final String TAG = "QSB.VoiceSearch";
-
- private final Context mContext;
-
- public VoiceSearch(Context context) {
- mContext = context;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- public boolean shouldShowVoiceSearch() {
- return isVoiceSearchAvailable();
- }
-
- protected Intent createVoiceSearchIntent() {
- return new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- }
-
- private ResolveInfo getResolveInfo() {
- Intent intent = createVoiceSearchIntent();
- ResolveInfo ri = mContext.getPackageManager().
- resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- return ri;
- }
-
- public boolean isVoiceSearchAvailable() {
- return getResolveInfo() != null;
- }
-
- public Intent createVoiceWebSearchIntent(Bundle appData) {
- if (!isVoiceSearchAvailable()) return null;
- Intent intent = createVoiceSearchIntent();
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
- RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
- if (appData != null) {
- intent.putExtra(SearchManager.APP_DATA, appData);
- }
- return intent;
- }
-
- /**
- * Create an intent to launch the voice search help screen, if any exists.
- * @return The intent, or null.
- */
- public Intent createVoiceSearchHelpIntent() {
- return null;
- }
-
- /**
- * Gets the {@code versionCode} of the currently installed voice search package.
- *
- * @return The {@code versionCode} of voiceSearch, or 0 if none is installed.
- */
- public int getVersion() {
- ResolveInfo ri = getResolveInfo();
- if (ri == null) return 0;
- ComponentInfo ci = ri.activityInfo != null ? ri.activityInfo : ri.serviceInfo;
- try {
- return getContext().getPackageManager().getPackageInfo(ci.packageName, 0).versionCode;
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Cannot find voice search package " + ci.packageName, e);
- return 0;
- }
- }
-
- public ComponentName getComponent() {
- return createVoiceSearchIntent().resolveActivity(getContext().getPackageManager());
- }
-}
diff --git a/src/com/android/quicksearchbox/VoiceSearch.kt b/src/com/android/quicksearchbox/VoiceSearch.kt
new file mode 100644
index 0000000..608e5d8
--- /dev/null
+++ b/src/com/android/quicksearchbox/VoiceSearch.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox
+
+import android.app.SearchManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.pm.ResolveInfo
+import android.os.Bundle
+import android.speech.RecognizerIntent
+import android.util.Log
+
+/** Voice Search integration. */
+class VoiceSearch(context: Context?) {
+
+ private val mContext: Context?
+
+ protected val context: Context?
+ get() = mContext
+
+ fun shouldShowVoiceSearch(): Boolean {
+ return isVoiceSearchAvailable
+ }
+
+ protected fun createVoiceSearchIntent(): Intent {
+ return Intent(RecognizerIntent.ACTION_WEB_SEARCH)
+ }
+
+ private val resolveInfo: ResolveInfo?
+ @Suppress("DEPRECATION")
+ get() {
+ val intent: Intent = createVoiceSearchIntent()
+ return mContext
+ ?.getPackageManager()
+ ?.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ }
+ val isVoiceSearchAvailable: Boolean
+ get() = resolveInfo != null
+
+ fun createVoiceWebSearchIntent(appData: Bundle?): Intent? {
+ if (!isVoiceSearchAvailable) return null
+ val intent: Intent = createVoiceSearchIntent()
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.putExtra(
+ RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH
+ )
+ if (appData != null) {
+ intent.putExtra(SearchManager.APP_DATA, appData)
+ }
+ return intent
+ }
+
+ /**
+ * Create an intent to launch the voice search help screen, if any exists.
+ * @return The intent, or null.
+ */
+ fun createVoiceSearchHelpIntent(): Intent? {
+ return null
+ }
+
+ /**
+ * Gets the `versionCode` of the currently installed voice search package.
+ *
+ * @return The `versionCode` of voiceSearch, or 0 if none is installed.
+ */
+ val version: Long
+ @Suppress("DEPRECATION")
+ get() {
+ val ri: ResolveInfo = resolveInfo ?: return 0
+ val ci: ComponentInfo = if (ri.activityInfo != null) ri.activityInfo else ri.serviceInfo
+ return try {
+ context!!.getPackageManager().getPackageInfo(ci.packageName, 0).getLongVersionCode()
+ } catch (e: NameNotFoundException) {
+ Log.e(TAG, "Cannot find voice search package " + ci.packageName, e)
+ 0
+ }
+ }
+ val component: ComponentName
+ get() = createVoiceSearchIntent().resolveActivity(context!!.getPackageManager())
+
+ companion object {
+ private const val TAG = "QSB.VoiceSearch"
+ }
+
+ init {
+ mContext = context
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/AbstractGoogleSource.java b/src/com/android/quicksearchbox/google/AbstractGoogleSource.java
deleted file mode 100644
index 2077777..0000000
--- a/src/com/android/quicksearchbox/google/AbstractGoogleSource.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.google;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-
-import com.android.quicksearchbox.AbstractInternalSource;
-import com.android.quicksearchbox.CursorBackedSourceResult;
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-
-/**
- * Special source implementation for Google suggestions.
- */
-public abstract class AbstractGoogleSource extends AbstractInternalSource implements GoogleSource {
-
- /*
- * This name corresponds to what was used in previous version of quick search box. We use the
- * same name so that shortcuts continue to work after an upgrade. (It also makes logging more
- * consistent).
- */
- private static final String GOOGLE_SOURCE_NAME =
- "com.android.quicksearchbox/.google.GoogleSearch";
-
- public AbstractGoogleSource(Context context, Handler uiThread, NamedTaskExecutor iconLoader) {
- super(context, uiThread, iconLoader);
- }
-
- @Override
- public abstract ComponentName getIntentComponent();
-
- @Override
- public abstract SuggestionCursor refreshShortcut(String shortcutId, String extraData);
-
- /**
- * Called by QSB to get web suggestions for a query.
- */
- @Override
- public abstract SourceResult queryInternal(String query);
-
- /**
- * Called by external apps to get web suggestions for a query.
- */
- @Override
- public abstract SourceResult queryExternal(String query);
-
- @Override
- public Intent createVoiceSearchIntent(Bundle appData) {
- return createVoiceWebSearchIntent(appData);
- }
-
- @Override
- public String getDefaultIntentAction() {
- return Intent.ACTION_WEB_SEARCH;
- }
-
- @Override
- public CharSequence getHint() {
- return getContext().getString(R.string.google_search_hint);
- }
-
- @Override
- public CharSequence getLabel() {
- return getContext().getString(R.string.google_search_label);
- }
-
- @Override
- public String getName() {
- return GOOGLE_SOURCE_NAME;
- }
-
- @Override
- public CharSequence getSettingsDescription() {
- return getContext().getString(R.string.google_search_description);
- }
-
- @Override
- protected int getSourceIconResource() {
- return R.mipmap.google_icon;
- }
-
- @Override
- public SourceResult getSuggestions(String query, int queryLimit) {
- return emptyIfNull(queryInternal(query), query);
- }
-
- public SourceResult getSuggestionsExternal(String query) {
- return emptyIfNull(queryExternal(query), query);
- }
-
- private SourceResult emptyIfNull(SourceResult result, String query) {
- return result == null ? new CursorBackedSourceResult(this, query) : result;
- }
-
- @Override
- public boolean voiceSearchEnabled() {
- return true;
- }
-
- @Override
- public boolean includeInAll() {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/google/AbstractGoogleSource.kt b/src/com/android/quicksearchbox/google/AbstractGoogleSource.kt
new file mode 100644
index 0000000..32e9367
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/AbstractGoogleSource.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import com.android.quicksearchbox.AbstractInternalSource
+import com.android.quicksearchbox.CursorBackedSourceResult
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.SourceResult
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.util.NamedTaskExecutor
+
+/** Special source implementation for Google suggestions. */
+abstract class AbstractGoogleSource(
+ context: Context?,
+ uiThread: Handler?,
+ iconLoader: NamedTaskExecutor
+) :
+ AbstractInternalSource(context, uiThread, iconLoader),
+ com.android.quicksearchbox.google.GoogleSource {
+ @get:Override abstract override val intentComponent: ComponentName?
+
+ @Override
+ abstract override fun refreshShortcut(shortcutId: String?, extraData: String?): SuggestionCursor?
+
+ /** Called by QSB to get web suggestions for a query. */
+ @Override abstract override fun queryInternal(query: String?): SourceResult?
+
+ /** Called by external apps to get web suggestions for a query. */
+ @Override abstract override fun queryExternal(query: String?): SourceResult?
+
+ @Override
+ override fun createVoiceSearchIntent(appData: Bundle?): Intent? {
+ return createVoiceWebSearchIntent(appData)
+ }
+
+ @get:Override
+ override val defaultIntentAction: String
+ get() = Intent.ACTION_WEB_SEARCH
+
+ @get:Override
+ override val hint: CharSequence
+ get() = context!!.getString(R.string.google_search_hint)
+
+ @get:Override
+ override val label: CharSequence
+ get() = context!!.getString(R.string.google_search_label)
+
+ @get:Override
+ override val name: String
+ get() = AbstractGoogleSource.Companion.GOOGLE_SOURCE_NAME
+
+ @get:Override
+ override val settingsDescription: CharSequence
+ get() = context!!.getString(R.string.google_search_description)
+
+ @get:Override
+ override val sourceIconResource: Int
+ get() = R.mipmap.google_icon
+
+ @Override
+ override fun getSuggestions(query: String?, queryLimit: Int): SourceResult? {
+ return emptyIfNull(queryInternal(query), query)
+ }
+
+ fun getSuggestionsExternal(query: String?): SourceResult {
+ return emptyIfNull(queryExternal(query), query)
+ }
+
+ private fun emptyIfNull(result: SourceResult?, query: String?): SourceResult {
+ return if (result == null) CursorBackedSourceResult(this, query) else result
+ }
+
+ @Override
+ override fun voiceSearchEnabled(): Boolean {
+ return true
+ }
+
+ @Override
+ override fun includeInAll(): Boolean {
+ return true
+ }
+
+ companion object {
+ /*
+ * This name corresponds to what was used in previous version of quick search box. We use the
+ * same name so that shortcuts continue to work after an upgrade. (It also makes logging more
+ * consistent).
+ */
+ private const val GOOGLE_SOURCE_NAME = "com.android.quicksearchbox/.google.GoogleSearch"
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.java b/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.java
deleted file mode 100644
index 6eb8f9d..0000000
--- a/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.google;
-
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionExtras;
-
-import android.content.ComponentName;
-import android.database.DataSetObserver;
-
-import java.util.Collection;
-
-public abstract class AbstractGoogleSourceResult implements SourceResult {
-
- private final Source mSource;
- private final String mUserQuery;
- private int mPos = 0;
-
- public AbstractGoogleSourceResult(Source source, String userQuery) {
- mSource = source;
- mUserQuery = userQuery;
- }
-
- public abstract int getCount();
-
- public abstract String getSuggestionQuery();
-
- public Source getSource() {
- return mSource;
- }
-
- public void close() {
- }
-
- public int getPosition() {
- return mPos;
- }
-
- public String getUserQuery() {
- return mUserQuery;
- }
-
- public void moveTo(int pos) {
- mPos = pos;
- }
-
- public boolean moveToNext() {
- int size = getCount();
- if (mPos >= size) {
- // Already past the end
- return false;
- }
- mPos++;
- return mPos < size;
- }
-
- public void registerDataSetObserver(DataSetObserver observer) {
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- }
-
- public String getSuggestionText1() {
- return getSuggestionQuery();
- }
-
- public Source getSuggestionSource() {
- return mSource;
- }
-
- public boolean isSuggestionShortcut() {
- return false;
- }
-
- public String getShortcutId() {
- return null;
- }
-
- public String getSuggestionFormat() {
- return null;
- }
-
- public String getSuggestionIcon1() {
- return String.valueOf(R.drawable.magnifying_glass);
- }
-
- public String getSuggestionIcon2() {
- return null;
- }
-
- public String getSuggestionIntentAction() {
- return mSource.getDefaultIntentAction();
- }
-
- public ComponentName getSuggestionIntentComponent() {
- return mSource.getIntentComponent();
- }
-
- public String getSuggestionIntentDataString() {
- return null;
- }
-
- public String getSuggestionIntentExtraData() {
- return null;
- }
-
- public String getSuggestionLogType() {
- return null;
- }
-
- public String getSuggestionText2() {
- return null;
- }
-
- public String getSuggestionText2Url() {
- return null;
- }
-
- public boolean isSpinnerWhileRefreshing() {
- return false;
- }
-
- public boolean isWebSearchSuggestion() {
- return true;
- }
-
- public boolean isHistorySuggestion() {
- return false;
- }
-
- public SuggestionExtras getExtras() {
- return null;
- }
-
- public Collection<String> getExtraColumns() {
- return null;
- }
-}
diff --git a/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.kt b/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.kt
new file mode 100644
index 0000000..9ee4d58
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/AbstractGoogleSourceResult.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.content.ComponentName
+import android.database.DataSetObserver
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Source
+import com.android.quicksearchbox.SourceResult
+import com.android.quicksearchbox.SuggestionExtras
+
+abstract class AbstractGoogleSourceResult(source: Source, userQuery: String) : SourceResult {
+ private val mSource: Source
+ override val userQuery: String
+ override var position = 0
+ abstract override val count: Int
+ abstract override val suggestionQuery: String?
+ override val source: Source
+ get() = mSource
+
+ override fun close() {}
+ override fun moveTo(pos: Int) {
+ position = pos
+ }
+
+ override fun moveToNext(): Boolean {
+ val size = count
+ if (position >= size) {
+ // Already past the end
+ return false
+ }
+ position++
+ return position < size
+ }
+
+ override fun registerDataSetObserver(observer: DataSetObserver?) {}
+ override fun unregisterDataSetObserver(observer: DataSetObserver?) {}
+ override val suggestionText1: String?
+ get() = suggestionQuery
+ override val suggestionSource: Source
+ get() = mSource
+ override val isSuggestionShortcut: Boolean
+ get() = false
+ override val shortcutId: String?
+ get() = null
+ override val suggestionFormat: String?
+ get() = null
+ override val suggestionIcon1: String
+ get() = R.drawable.magnifying_glass.toString()
+ override val suggestionIcon2: String?
+ get() = null
+ override val suggestionIntentAction: String?
+ get() = mSource.defaultIntentAction
+ override val suggestionIntentComponent: ComponentName?
+ get() = mSource.intentComponent
+ override val suggestionIntentDataString: String?
+ get() = null
+ override val suggestionIntentExtraData: String?
+ get() = null
+ override val suggestionLogType: String?
+ get() = null
+ override val suggestionText2: String?
+ get() = null
+ override val suggestionText2Url: String?
+ get() = null
+ override val isSpinnerWhileRefreshing: Boolean
+ get() = false
+ override val isWebSearchSuggestion: Boolean
+ get() = true
+ override val isHistorySuggestion: Boolean
+ get() = false
+ override val extras: SuggestionExtras?
+ get() = null
+ override val extraColumns: Collection<String>?
+ get() = null
+
+ init {
+ mSource = source
+ this.userQuery = userQuery
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/GoogleSearch.java b/src/com/android/quicksearchbox/google/GoogleSearch.java
deleted file mode 100644
index 58755d8..0000000
--- a/src/com/android/quicksearchbox/google/GoogleSearch.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.google;
-
-import com.android.common.Search;
-import com.android.quicksearchbox.QsbApplication;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.location.Location;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Browser;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Locale;
-
-/**
- * This class is purely here to get search queries and route them to
- * the global {@link Intent#ACTION_WEB_SEARCH}.
- */
-public class GoogleSearch extends Activity {
- private static final String TAG = "GoogleSearch";
- private static final boolean DBG = false;
-
- // Used to figure out which domain to base search requests
- // on.
- private SearchBaseUrlHelper mSearchDomainHelper;
-
- // "source" parameter for Google search requests from unknown sources (e.g. apps). This will get
- // prefixed with the string 'android-' before being sent on the wire.
- final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Intent intent = getIntent();
- String action = intent != null ? intent.getAction() : null;
-
- // This should probably be moved so as to
- // send out the request to /checksearchdomain as early as possible.
- mSearchDomainHelper = QsbApplication.get(this).getSearchBaseUrlHelper();
-
- if (Intent.ACTION_WEB_SEARCH.equals(action) || Intent.ACTION_SEARCH.equals(action)) {
- handleWebSearchIntent(intent);
- }
-
- finish();
- }
-
- /**
- * Construct the language code (hl= paramater) for the given locale.
- */
- public static String getLanguage(Locale locale) {
- String language = locale.getLanguage();
- StringBuilder hl = new StringBuilder(language);
- String country = locale.getCountry();
-
- if (!TextUtils.isEmpty(country) && useLangCountryHl(language, country)) {
- hl.append('-');
- hl.append(country);
- }
-
- if (DBG) Log.d(TAG, "language " + language + ", country " + country + " -> hl=" + hl);
- return hl.toString();
- }
-
- // TODO: This is a workaround for bug 3232296. When that is fixed, this method can be removed.
- private static boolean useLangCountryHl(String language, String country) {
- // lang-country is currently only supported for a small number of locales
- if ("en".equals(language)) {
- return "GB".equals(country);
- } else if ("zh".equals(language)) {
- return "CN".equals(country) || "TW".equals(country);
- } else if ("pt".equals(language)) {
- return "BR".equals(country) || "PT".equals(country);
- } else {
- return false;
- }
- }
-
- private void handleWebSearchIntent(Intent intent) {
- Intent launchUriIntent = createLaunchUriIntentFromSearchIntent(intent);
- PendingIntent pending =
- intent.getParcelableExtra(SearchManager.EXTRA_WEB_SEARCH_PENDINGINTENT);
- if (pending == null || !launchPendingIntent(pending, launchUriIntent)) {
- launchIntent(launchUriIntent);
- }
- }
-
- private Intent createLaunchUriIntentFromSearchIntent(Intent intent) {
- String query = intent.getStringExtra(SearchManager.QUERY);
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "Got search intent with no query.");
- return null;
- }
-
- // If the caller specified a 'source' url parameter, use that and if not use default.
- Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
- String source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
- if (appSearchData != null) {
- source = appSearchData.getString(Search.SOURCE);
- }
-
- // The browser can pass along an application id which it uses to figure out which
- // window to place a new search into. So if this exists, we'll pass it back to
- // the browser. Otherwise, add our own package name as the application id, so that
- // the browser can organize all searches launched from this provider together.
- String applicationId = intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
- if (applicationId == null) {
- applicationId = getPackageName();
- }
-
- try {
- String searchUri = mSearchDomainHelper.getSearchBaseUrl()
- + "&source=android-" + source
- + "&q=" + URLEncoder.encode(query, "UTF-8");
- Intent launchUriIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
- launchUriIntent.putExtra(Browser.EXTRA_APPLICATION_ID, applicationId);
- launchUriIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return launchUriIntent;
- } catch (UnsupportedEncodingException e) {
- Log.w(TAG, "Error", e);
- return null;
- }
-
- }
-
- private void launchIntent(Intent intent) {
- try {
- Log.i(TAG, "Launching intent: " + intent.toUri(0));
- startActivity(intent);
- } catch (ActivityNotFoundException ex) {
- Log.w(TAG, "No activity found to handle: " + intent);
- }
- }
-
- private boolean launchPendingIntent(PendingIntent pending, Intent fillIn) {
- try {
- pending.send(this, Activity.RESULT_OK, fillIn);
- return true;
- } catch (PendingIntent.CanceledException ex) {
- Log.i(TAG, "Pending intent cancelled: " + pending);
- return false;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/google/GoogleSearch.kt b/src/com/android/quicksearchbox/google/GoogleSearch.kt
new file mode 100644
index 0000000..9c01cfe
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/GoogleSearch.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.app.SearchManager
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Browser
+import android.text.TextUtils
+import android.util.Log
+import com.android.common.Search
+import com.android.quicksearchbox.QsbApplication
+import java.io.UnsupportedEncodingException
+import java.net.URLEncoder
+import java.util.Locale
+
+/**
+ * This class is purely here to get search queries and route them to the global
+ * [Intent.ACTION_WEB_SEARCH].
+ */
+class GoogleSearch : Activity() {
+ // Used to figure out which domain to base search requests
+ // on.
+ private var mSearchDomainHelper: SearchBaseUrlHelper? = null
+
+ @Override
+ protected override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val intent: Intent? = getIntent()
+ val action: String? = if (intent != null) intent.getAction() else null
+
+ // This should probably be moved so as to
+ // send out the request to /checksearchdomain as early as possible.
+ mSearchDomainHelper = QsbApplication.get(this).searchBaseUrlHelper
+ if (Intent.ACTION_WEB_SEARCH.equals(action) || Intent.ACTION_SEARCH.equals(action)) {
+ handleWebSearchIntent(intent)
+ }
+ finish()
+ }
+
+ private fun handleWebSearchIntent(intent: Intent?) {
+ val launchUriIntent: Intent? = createLaunchUriIntentFromSearchIntent(intent)
+
+ @Suppress("DEPRECATION")
+ val pending: PendingIntent? =
+ intent?.getParcelableExtra(SearchManager.EXTRA_WEB_SEARCH_PENDINGINTENT)
+ if (pending == null || !launchPendingIntent(pending, launchUriIntent)) {
+ launchIntent(launchUriIntent)
+ }
+ }
+
+ private fun createLaunchUriIntentFromSearchIntent(intent: Intent?): Intent? {
+ val query: String? = intent?.getStringExtra(SearchManager.QUERY)
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "Got search intent with no query.")
+ return null
+ }
+
+ // If the caller specified a 'source' url parameter, use that and if not use default.
+ val appSearchData: Bundle? = intent?.getBundleExtra(SearchManager.APP_DATA)
+ var source: String? = GoogleSearch.Companion.GOOGLE_SEARCH_SOURCE_UNKNOWN
+ if (appSearchData != null) {
+ source = appSearchData.getString(Search.SOURCE)
+ }
+
+ // The browser can pass along an application id which it uses to figure out which
+ // window to place a new search into. So if this exists, we'll pass it back to
+ // the browser. Otherwise, add our own package name as the application id, so that
+ // the browser can organize all searches launched from this provider together.
+ var applicationId: String? = intent?.getStringExtra(Browser.EXTRA_APPLICATION_ID)
+ if (applicationId == null) {
+ applicationId = getPackageName()
+ }
+ return try {
+ val searchUri =
+ (mSearchDomainHelper!!.searchBaseUrl.toString() +
+ "&source=android-" +
+ source +
+ "&q=" +
+ URLEncoder.encode(query, "UTF-8"))
+ val launchUriIntent = Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))
+ launchUriIntent.putExtra(Browser.EXTRA_APPLICATION_ID, applicationId)
+ launchUriIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ launchUriIntent
+ } catch (e: UnsupportedEncodingException) {
+ Log.w(TAG, "Error", e)
+ null
+ }
+ }
+
+ private fun launchIntent(intent: Intent?) {
+ try {
+ Log.i(TAG, "Launching intent: " + intent?.toUri(0))
+ startActivity(intent)
+ } catch (ex: ActivityNotFoundException) {
+ Log.w(TAG, "No activity found to handle: $intent")
+ }
+ }
+
+ private fun launchPendingIntent(pending: PendingIntent, fillIn: Intent?): Boolean {
+ return try {
+ pending.send(this, Activity.RESULT_OK, fillIn)
+ true
+ } catch (ex: PendingIntent.CanceledException) {
+ Log.i(TAG, "Pending intent cancelled: $pending")
+ false
+ }
+ }
+
+ companion object {
+ private const val TAG = "GoogleSearch"
+ private const val DBG = false
+
+ // "source" parameter for Google search requests from unknown sources (e.g. apps). This will get
+ // prefixed with the string 'android-' before being sent on the wire.
+ const val GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"
+
+ /** Construct the language code (hl= parameter) for the given locale. */
+ fun getLanguage(locale: Locale): String {
+ val language: String = locale.getLanguage()
+ val hl: StringBuilder = StringBuilder(language)
+ val country: String = locale.getCountry()
+ if (!TextUtils.isEmpty(country) && useLangCountryHl(language, country)) {
+ hl.append('-')
+ hl.append(country)
+ }
+ if (DBG) Log.d(TAG, "language $language, country $country -> hl=$hl")
+ return hl.toString()
+ }
+
+ // TODO: This is a workaround for bug 3232296. When that is fixed, this method can be removed.
+ private fun useLangCountryHl(language: String, country: String): Boolean {
+ // lang-country is currently only supported for a small number of locales
+ return if ("en".equals(language)) {
+ "GB".equals(country)
+ } else if ("zh".equals(language)) {
+ "CN".equals(country) || "TW".equals(country)
+ } else if ("pt".equals(language)) {
+ "BR".equals(country) || "PT".equals(country)
+ } else {
+ false
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/GoogleSource.java b/src/com/android/quicksearchbox/google/GoogleSource.java
deleted file mode 100644
index b566817..0000000
--- a/src/com/android/quicksearchbox/google/GoogleSource.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.google;
-
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionCursor;
-
-/**
- * Special source interface for Google suggestions.
- */
-public interface GoogleSource extends Source {
-
- SuggestionCursor refreshShortcut(String shortcutId, String extraData);
-
- /**
- * Called by QSB to get web suggestions for a query.
- */
- SourceResult queryInternal(String query);
-
- /**
- * Called by external apps to get web suggestions for a query.
- */
- SourceResult queryExternal(String query);
-
-}
diff --git a/src/com/android/quicksearchbox/google/GoogleSource.kt b/src/com/android/quicksearchbox/google/GoogleSource.kt
new file mode 100644
index 0000000..1a82211
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/GoogleSource.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import com.android.quicksearchbox.Source
+import com.android.quicksearchbox.SourceResult
+import com.android.quicksearchbox.SuggestionCursor
+
+/** Special source interface for Google suggestions. */
+interface GoogleSource : Source {
+ fun refreshShortcut(shortcutId: String?, extraData: String?): SuggestionCursor?
+
+ /** Called by QSB to get web suggestions for a query. */
+ fun queryInternal(query: String?): SourceResult?
+
+ /** Called by external apps to get web suggestions for a query. */
+ fun queryExternal(query: String?): SourceResult?
+}
diff --git a/src/com/android/quicksearchbox/google/GoogleSuggestClient.java b/src/com/android/quicksearchbox/google/GoogleSuggestClient.java
deleted file mode 100644
index 51c5129..0000000
--- a/src/com/android/quicksearchbox/google/GoogleSuggestClient.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.google;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Build;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.quicksearchbox.Config;
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.util.NamedTaskExecutor;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.Locale;
-
-/**
- * Use network-based Google Suggests to provide search suggestions.
- */
-public class GoogleSuggestClient extends AbstractGoogleSource {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "GoogleSearch";
-
- private static final String USER_AGENT = "Android/" + Build.VERSION.RELEASE;
- private String mSuggestUri;
-
- // TODO: this should be defined somewhere
- private static final String HTTP_TIMEOUT = "http.conn-manager.timeout";
-
- private final int mConnectTimeout;
-
- public GoogleSuggestClient(Context context, Handler uiThread,
- NamedTaskExecutor iconLoader, Config config) {
- super(context, uiThread, iconLoader);
-
- mConnectTimeout = config.getHttpConnectTimeout();
- // NOTE: Do not look up the resource here; Localization changes may not have completed
- // yet (e.g. we may still be reading the SIM card).
- mSuggestUri = null;
- }
-
- @Override
- public ComponentName getIntentComponent() {
- return new ComponentName(getContext(), GoogleSearch.class);
- }
-
- @Override
- public SourceResult queryInternal(String query) {
- return query(query);
- }
-
- @Override
- public SourceResult queryExternal(String query) {
- return query(query);
- }
-
- /**
- * Queries for a given search term and returns a cursor containing
- * suggestions ordered by best match.
- */
- private SourceResult query(String query) {
- if (TextUtils.isEmpty(query)) {
- return null;
- }
- if (!isNetworkConnected()) {
- Log.i(LOG_TAG, "Not connected to network.");
- return null;
- }
- HttpURLConnection connection = null;
- try {
- String encodedQuery = URLEncoder.encode(query, "UTF-8");
- if (mSuggestUri == null) {
- Locale l = Locale.getDefault();
- String language = GoogleSearch.getLanguage(l);
- mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base,
- language);
- }
-
- String suggestUri = mSuggestUri + encodedQuery;
- if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri);
- URL url = URI.create(suggestUri).toURL();
- connection = (HttpURLConnection) url.openConnection();
- connection.setConnectTimeout(mConnectTimeout);
- connection.setRequestProperty("User-Agent", USER_AGENT);
- connection.setRequestMethod("GET");
- connection.setDoInput(true);
- connection.connect();
- InputStream inputStream = connection.getInputStream();
- if (connection.getResponseCode() == 200) {
-
- /* Goto http://www.google.com/complete/search?json=true&q=foo
- * to see what the data format looks like. It's basically a json
- * array containing 4 other arrays. We only care about the middle
- * 2 which contain the suggestions and their popularity.
- */
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- StringBuilder sb = new StringBuilder();
- String line;
- while ((line = reader.readLine()) != null) {
- sb.append(line).append("\n");
- }
- reader.close();
- JSONArray results = new JSONArray(sb.toString());
- JSONArray suggestions = results.getJSONArray(1);
- JSONArray popularity = results.getJSONArray(2);
- if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results");
- return new GoogleSuggestCursor(this, query, suggestions, popularity);
- } else {
- if (DBG)
- Log.d(LOG_TAG, "Request failed " + connection.getResponseMessage());
- }
- } catch (UnsupportedEncodingException e) {
- Log.w(LOG_TAG, "Error", e);
- } catch (IOException e) {
- Log.w(LOG_TAG, "Error", e);
- } catch (JSONException e) {
- Log.w(LOG_TAG, "Error", e);
- } finally {
- if (connection != null) connection.disconnect();
- }
- return null;
- }
-
- @Override
- public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) {
- return null;
- }
-
- private boolean isNetworkConnected() {
- NetworkInfo networkInfo = getActiveNetworkInfo();
- return networkInfo != null && networkInfo.isConnected();
- }
-
- private NetworkInfo getActiveNetworkInfo() {
- ConnectivityManager connectivity =
- (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- if (connectivity == null) {
- return null;
- }
- return connectivity.getActiveNetworkInfo();
- }
-
- private static class GoogleSuggestCursor extends AbstractGoogleSourceResult {
-
- /* Contains the actual suggestions */
- private final JSONArray mSuggestions;
-
- /* This contains the popularity of each suggestion
- * i.e. 165,000 results. It's not related to sorting.
- */
- private final JSONArray mPopularity;
-
- public GoogleSuggestCursor(Source source, String userQuery,
- JSONArray suggestions, JSONArray popularity) {
- super(source, userQuery);
- mSuggestions = suggestions;
- mPopularity = popularity;
- }
-
- @Override
- public int getCount() {
- return mSuggestions.length();
- }
-
- @Override
- public String getSuggestionQuery() {
- try {
- return mSuggestions.getString(getPosition());
- } catch (JSONException e) {
- Log.w(LOG_TAG, "Error parsing response: " + e);
- return null;
- }
- }
-
- @Override
- public String getSuggestionText2() {
- try {
- return mPopularity.getString(getPosition());
- } catch (JSONException e) {
- Log.w(LOG_TAG, "Error parsing response: " + e);
- return null;
- }
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/google/GoogleSuggestClient.kt b/src/com/android/quicksearchbox/google/GoogleSuggestClient.kt
new file mode 100644
index 0000000..610cfdd
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/GoogleSuggestClient.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.content.ComponentName
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
+import android.os.Handler
+import android.text.TextUtils
+import android.util.Log
+import com.android.quicksearchbox.Config
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Source
+import com.android.quicksearchbox.SourceResult
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.util.NamedTaskExecutor
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.UnsupportedEncodingException
+import java.net.HttpURLConnection
+import java.net.URI
+import java.net.URL
+import java.net.URLEncoder
+import java.util.Locale
+import org.json.JSONArray
+import org.json.JSONException
+
+/** Use network-based Google Suggests to provide search suggestions. */
+class GoogleSuggestClient(
+ context: Context?,
+ uiThread: Handler?,
+ iconLoader: NamedTaskExecutor,
+ config: Config
+) : AbstractGoogleSource(context, uiThread, iconLoader) {
+ private var mSuggestUri: String?
+ private val mConnectTimeout: Int
+
+ @get:Override
+ override val intentComponent: ComponentName
+ get() = ComponentName(context!!, GoogleSearch::class.java)
+
+ @Override
+ override fun queryInternal(query: String?): SourceResult? {
+ return query(query)
+ }
+
+ @Override
+ override fun queryExternal(query: String?): SourceResult? {
+ return query(query)
+ }
+
+ /**
+ * Queries for a given search term and returns a cursor containing suggestions ordered by best
+ * match.
+ */
+ private fun query(query: String?): SourceResult? {
+ if (TextUtils.isEmpty(query)) {
+ return null
+ }
+ if (!isNetworkConnected) {
+ Log.i(LOG_TAG, "Not connected to network.")
+ return null
+ }
+ var connection: HttpURLConnection? = null
+ try {
+ val encodedQuery: String = URLEncoder.encode(query, "UTF-8")
+ if (mSuggestUri == null) {
+ val l: Locale = Locale.getDefault()
+ val language: String = GoogleSearch.getLanguage(l)
+ mSuggestUri = context?.getResources()!!.getString(R.string.google_suggest_base, language)
+ }
+ val suggestUri = mSuggestUri + encodedQuery
+ if (DBG) Log.d(LOG_TAG, "Sending request: $suggestUri")
+ val url: URL = URI.create(suggestUri).toURL()
+ connection = url.openConnection() as HttpURLConnection
+ connection.setConnectTimeout(mConnectTimeout)
+ connection.setRequestProperty("User-Agent", USER_AGENT)
+ connection.setRequestMethod("GET")
+ connection.setDoInput(true)
+ connection.connect()
+ val inputStream: InputStream = connection.getInputStream()
+ if (connection.getResponseCode() == 200) {
+
+ /* Goto http://www.google.com/complete/search?json=true&q=foo
+ * to see what the data format looks like. It's basically a json
+ * array containing 4 other arrays. We only care about the middle
+ * 2 which contain the suggestions and their popularity.
+ */
+ val reader = BufferedReader(InputStreamReader(inputStream))
+ val sb: StringBuilder = StringBuilder()
+ var line: String?
+ while (reader.readLine().also { line = it } != null) {
+ sb.append(line).append("\n")
+ }
+ reader.close()
+ val results = JSONArray(sb.toString())
+ val suggestions: JSONArray = results.getJSONArray(1)
+ val popularity: JSONArray = results.getJSONArray(2)
+ if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length().toString() + " results")
+ return GoogleSuggestCursor(this, query, suggestions, popularity)
+ } else {
+ if (DBG) Log.d(LOG_TAG, "Request failed " + connection.getResponseMessage())
+ }
+ } catch (e: UnsupportedEncodingException) {
+ Log.w(LOG_TAG, "Error", e)
+ } catch (e: IOException) {
+ Log.w(LOG_TAG, "Error", e)
+ } catch (e: JSONException) {
+ Log.w(LOG_TAG, "Error", e)
+ } finally {
+ if (connection != null) connection.disconnect()
+ }
+ return null
+ }
+
+ @Override
+ override fun refreshShortcut(shortcutId: String?, extraData: String?): SuggestionCursor? {
+ return null
+ }
+
+ private val isNetworkConnected: Boolean
+ get() {
+ val actNC = activeNetworkCapabilities
+ return actNC != null && actNC.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }
+ private val activeNetworkCapabilities: NetworkCapabilities?
+ get() {
+ val connectivityManager =
+ context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetwork = connectivityManager.getActiveNetwork()
+ return connectivityManager.getNetworkCapabilities(activeNetwork)
+ }
+
+ private class GoogleSuggestCursor(
+ source: Source,
+ userQuery: String?,
+ suggestions: JSONArray,
+ popularity: JSONArray
+ ) : AbstractGoogleSourceResult(source, userQuery!!) {
+ /* Contains the actual suggestions */
+ private val mSuggestions: JSONArray
+
+ /* This contains the popularity of each suggestion
+ * i.e. 165,000 results. It's not related to sorting.
+ */
+ private val mPopularity: JSONArray
+
+ @get:Override
+ override val count: Int
+ get() = mSuggestions.length()
+
+ @get:Override
+ override val suggestionQuery: String?
+ get() =
+ try {
+ mSuggestions.getString(position)
+ } catch (e: JSONException) {
+ Log.w(LOG_TAG, "Error parsing response: $e")
+ null
+ }
+
+ @get:Override
+ override val suggestionText2: String?
+ get() =
+ try {
+ mPopularity.getString(position)
+ } catch (e: JSONException) {
+ Log.w(LOG_TAG, "Error parsing response: $e")
+ null
+ }
+
+ init {
+ mSuggestions = suggestions
+ mPopularity = popularity
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val LOG_TAG = "GoogleSearch"
+ private val USER_AGENT = "Android/" + Build.VERSION.RELEASE
+
+ // TODO: this should be defined somewhere
+ private const val HTTP_TIMEOUT = "http.conn-manager.timeout"
+ }
+
+ init {
+ mConnectTimeout = config.httpConnectTimeout
+ // NOTE: Do not look up the resource here; Localization changes may not have completed
+ // yet (e.g. we may still be reading the SIM card).
+ mSuggestUri = null
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.java b/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.java
deleted file mode 100644
index 02f9d38..0000000
--- a/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.google;
-
-import com.android.quicksearchbox.CursorBackedSourceResult;
-import com.android.quicksearchbox.QsbApplication;
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionCursorBackedCursor;
-
-import android.app.SearchManager;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-
-/**
- * A suggestion provider which provides content from Genie, a service that offers
- * a superset of the content provided by Google Suggest.
- */
-public class GoogleSuggestionProvider extends ContentProvider {
- private static final boolean DBG = false;
- private static final String TAG = "QSB.GoogleSuggestionProvider";
-
- // UriMatcher constants
- private static final int SEARCH_SUGGEST = 0;
- private static final int SEARCH_SHORTCUT = 1;
-
- private UriMatcher mUriMatcher;
-
- private GoogleSource mSource;
-
- @Override
- public boolean onCreate() {
- mSource = QsbApplication.get(getContext()).getGoogleSource();
- mUriMatcher = buildUriMatcher(getContext());
- return true;
- }
-
- /**
- * This will always return {@link SearchManager#SUGGEST_MIME_TYPE} as this
- * provider is purely to provide suggestions.
- */
- @Override
- public String getType(Uri uri) {
- return SearchManager.SUGGEST_MIME_TYPE;
- }
-
- private SourceResult emptyIfNull(SourceResult result, GoogleSource source, String query) {
- return result == null ? new CursorBackedSourceResult(source, query) : result;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
-
- if (DBG) Log.d(TAG, "query uri=" + uri);
- int match = mUriMatcher.match(uri);
-
- if (match == SEARCH_SUGGEST) {
- String query = getQuery(uri);
- return new SuggestionCursorBackedCursor(
- emptyIfNull(mSource.queryExternal(query), mSource, query));
- } else if (match == SEARCH_SHORTCUT) {
- String shortcutId = getQuery(uri);
- String extraData =
- uri.getQueryParameter(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
- return new SuggestionCursorBackedCursor(mSource.refreshShortcut(shortcutId, extraData));
- } else {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- }
-
- /**
- * Gets the search text from a uri.
- */
- private String getQuery(Uri uri) {
- if (uri.getPathSegments().size() > 1) {
- return uri.getLastPathSegment();
- } else {
- return "";
- }
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- private UriMatcher buildUriMatcher(Context context) {
- String authority = getAuthority(context);
- UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
- matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY,
- SEARCH_SUGGEST);
- matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
- SEARCH_SUGGEST);
- matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT,
- SEARCH_SHORTCUT);
- matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*",
- SEARCH_SHORTCUT);
- return matcher;
- }
-
- protected String getAuthority(Context context) {
- return context.getPackageName() + ".google";
- }
-
-}
diff --git a/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.kt b/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.kt
new file mode 100644
index 0000000..337d7fc
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/GoogleSuggestionProvider.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.app.SearchManager
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.database.Cursor
+import android.net.Uri
+import android.util.Log
+import com.android.quicksearchbox.CursorBackedSourceResult
+import com.android.quicksearchbox.QsbApplication
+import com.android.quicksearchbox.SourceResult
+import com.android.quicksearchbox.SuggestionCursorBackedCursor
+
+/**
+ * A suggestion provider which provides content from Genie, a service that offers a superset of the
+ * content provided by Google Suggest.
+ */
+class GoogleSuggestionProvider : ContentProvider() {
+ private var mUriMatcher: UriMatcher? = null
+ private var mSource: GoogleSource? = null
+
+ @Override
+ override fun onCreate(): Boolean {
+ mSource = QsbApplication.get(getContext()).googleSource
+ mUriMatcher = buildUriMatcher(getContext())
+ return true
+ }
+
+ /**
+ * This will always return [SearchManager.SUGGEST_MIME_TYPE] as this provider is purely to provide
+ * suggestions.
+ */
+ @Override
+ override fun getType(uri: Uri): String? {
+ return SearchManager.SUGGEST_MIME_TYPE
+ }
+
+ private fun emptyIfNull(
+ result: SourceResult?,
+ source: GoogleSource?,
+ query: String?
+ ): SourceResult {
+ return result ?: CursorBackedSourceResult(source, query)
+ }
+
+ @Override
+ override fun query(
+ uri: Uri,
+ projection: Array<String?>?,
+ selection: String?,
+ selectionArgs: Array<String?>?,
+ sortOrder: String?
+ ): Cursor {
+ if (GoogleSuggestionProvider.Companion.DBG)
+ Log.d(GoogleSuggestionProvider.Companion.TAG, "query uri=$uri")
+ val match: Int? = mUriMatcher?.match(uri)
+ return if (match == GoogleSuggestionProvider.Companion.SEARCH_SUGGEST) {
+ val query = getQuery(uri)
+ SuggestionCursorBackedCursor(emptyIfNull(mSource!!.queryExternal(query), mSource, query))
+ } else if (match == GoogleSuggestionProvider.Companion.SEARCH_SHORTCUT) {
+ val shortcutId = getQuery(uri)
+ val extraData: String? = uri.getQueryParameter(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA)
+ SuggestionCursorBackedCursor(mSource!!.refreshShortcut(shortcutId, extraData))
+ } else {
+ throw IllegalArgumentException("Unknown URI $uri")
+ }
+ }
+
+ /** Gets the search text from a uri. */
+ private fun getQuery(uri: Uri): String? {
+ return if (uri.getPathSegments().size > 1) {
+ uri.getLastPathSegment()
+ } else {
+ ""
+ }
+ }
+
+ @Override
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException()
+ }
+
+ @Override
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String?>?
+ ): Int {
+ throw UnsupportedOperationException()
+ }
+
+ @Override
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String?>?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ private fun buildUriMatcher(context: Context?): UriMatcher {
+ val authority = getAuthority(context)
+ val matcher = UriMatcher(UriMatcher.NO_MATCH)
+ matcher.addURI(
+ authority,
+ SearchManager.SUGGEST_URI_PATH_QUERY,
+ GoogleSuggestionProvider.Companion.SEARCH_SUGGEST
+ )
+ matcher.addURI(
+ authority,
+ SearchManager.SUGGEST_URI_PATH_QUERY.toString() + "/*",
+ GoogleSuggestionProvider.Companion.SEARCH_SUGGEST
+ )
+ matcher.addURI(
+ authority,
+ SearchManager.SUGGEST_URI_PATH_SHORTCUT,
+ GoogleSuggestionProvider.Companion.SEARCH_SHORTCUT
+ )
+ matcher.addURI(
+ authority,
+ SearchManager.SUGGEST_URI_PATH_SHORTCUT.toString() + "/*",
+ GoogleSuggestionProvider.Companion.SEARCH_SHORTCUT
+ )
+ return matcher
+ }
+
+ protected fun getAuthority(context: Context?): String {
+ return context?.getPackageName().toString() + ".google"
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.GoogleSuggestionProvider"
+
+ // UriMatcher constants
+ private const val SEARCH_SUGGEST = 0
+ private const val SEARCH_SHORTCUT = 1
+ }
+}
diff --git a/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.java b/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.java
deleted file mode 100644
index d95214f..0000000
--- a/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.google;
-
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.SearchSettings;
-import com.android.quicksearchbox.SearchSettingsImpl;
-import com.android.quicksearchbox.util.HttpHelper;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.Locale;
-
-/**
- * Helper to build the base URL for all search requests.
- */
-public class SearchBaseUrlHelper implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SearchBaseUrlHelper";
-
- private static final String DOMAIN_CHECK_URL =
- "https://www.google.com/searchdomaincheck?format=domain";
-
- private static final long SEARCH_BASE_URL_EXPIRY_MS = 24 * 3600 * 1000L;
-
- private final HttpHelper mHttpHelper;
- private final Context mContext;
- private final SearchSettings mSearchSettings;
-
- /**
- * Note that this constructor will spawn a thread to issue a HTTP
- * request if shouldUseGoogleCom is false.
- */
- public SearchBaseUrlHelper(Context context, HttpHelper helper,
- SearchSettings searchSettings, SharedPreferences prefs) {
- mHttpHelper = helper;
- mContext = context;
- mSearchSettings = searchSettings;
-
- // Note: This earlier used an inner class, but that causes issues
- // because SharedPreferencesImpl uses a WeakHashMap< > and the listener
- // will be GC'ed unless we keep a reference to it here.
- prefs.registerOnSharedPreferenceChangeListener(this);
-
- maybeUpdateBaseUrlSetting(false);
- }
-
- /**
- * Update the base search url, either:
- * (a) it has never been set (first run)
- * (b) it has expired
- * (c) if the caller forces an update by setting the "force" parameter.
- *
- * @param force if true, then the URL is reset whether or not it has
- * expired.
- */
- public void maybeUpdateBaseUrlSetting(boolean force) {
- long lastUpdateTime = mSearchSettings.getSearchBaseDomainApplyTime();
- long currentTime = System.currentTimeMillis();
-
- if (force || lastUpdateTime == -1 ||
- currentTime - lastUpdateTime >= SEARCH_BASE_URL_EXPIRY_MS) {
- if (mSearchSettings.shouldUseGoogleCom()) {
- setSearchBaseDomain(getDefaultBaseDomain());
- } else {
- checkSearchDomain();
- }
- }
- }
-
- /**
- * @return the base url for searches.
- */
- public String getSearchBaseUrl() {
- return mContext.getResources().getString(R.string.google_search_base_pattern,
- getSearchDomain(), GoogleSearch.getLanguage(Locale.getDefault()));
- }
-
- /**
- * @return the search domain. This is of the form "google.co.xx" or "google.com",
- * used by UI code.
- */
- public String getSearchDomain() {
- String domain = mSearchSettings.getSearchBaseDomain();
-
- if (domain == null) {
- if (DBG) {
- Log.w(TAG, "Search base domain was null, last apply time=" +
- mSearchSettings.getSearchBaseDomainApplyTime());
- }
-
- // This is required to deal with the case wherein getSearchDomain
- // is called before checkSearchDomain returns a valid URL. This will
- // happen *only* on the first run of the app when the "use google.com"
- // option is unchecked. In other cases, the previously set domain (or
- // the default) will be returned.
- //
- // We have no choice in this case but to use the default search domain.
- domain = getDefaultBaseDomain();
- }
-
- if (domain.startsWith(".")) {
- if (DBG) Log.d(TAG, "Prepending www to " + domain);
- domain = "www" + domain;
- }
- return domain;
- }
-
- /**
- * Issue a request to google.com/searchdomaincheck to retrieve the base
- * URL for search requests.
- */
- private void checkSearchDomain() {
- final HttpHelper.GetRequest request = new HttpHelper.GetRequest(DOMAIN_CHECK_URL);
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void ... params) {
- if (DBG) Log.d(TAG, "Starting request to /searchdomaincheck");
- String domain;
- try {
- domain = mHttpHelper.get(request);
- } catch (Exception e) {
- if (DBG) Log.d(TAG, "Request to /searchdomaincheck failed : " + e);
- // Swallow any exceptions thrown by the HTTP helper, in
- // this rare case, we just use the default URL.
- domain = getDefaultBaseDomain();
-
- return null;
- }
-
- if (DBG) Log.d(TAG, "Request to /searchdomaincheck succeeded");
- setSearchBaseDomain(domain);
-
- return null;
- }
- }.execute();
- }
-
- private String getDefaultBaseDomain() {
- return mContext.getResources().getString(R.string.default_search_domain);
- }
-
- private void setSearchBaseDomain(String domain) {
- if (DBG) Log.d(TAG, "Setting search domain to : " + domain);
-
- mSearchSettings.setSearchBaseDomain(domain);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences pref, String key) {
- // Listen for changes only to the SEARCH_BASE_URL preference.
- if (DBG) Log.d(TAG, "Handling changed preference : " + key);
- if (SearchSettingsImpl.USE_GOOGLE_COM_PREF.equals(key)) {
- maybeUpdateBaseUrlSetting(true);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.kt b/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.kt
new file mode 100644
index 0000000..b78d49e
--- /dev/null
+++ b/src/com/android/quicksearchbox/google/SearchBaseUrlHelper.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.google
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.Log
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.SearchSettings
+import com.android.quicksearchbox.SearchSettingsImpl
+import com.android.quicksearchbox.util.HttpHelper
+import java.util.Locale
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+
+/** Helper to build the base URL for all search requests. */
+class SearchBaseUrlHelper(
+ context: Context?,
+ helper: HttpHelper,
+ searchSettings: SearchSettings,
+ prefs: SharedPreferences
+) : SharedPreferences.OnSharedPreferenceChangeListener {
+ private val mHttpHelper: HttpHelper
+ private val mContext: Context?
+ private val mSearchSettings: SearchSettings
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ /**
+ * Update the base search url, either: (a) it has never been set (first run) (b) it has expired
+ * (c) if the caller forces an update by setting the "force" parameter.
+ *
+ * @param force if true, then the URL is reset whether or not it has expired.
+ */
+ fun maybeUpdateBaseUrlSetting(force: Boolean) {
+ val lastUpdateTime: Long = mSearchSettings.searchBaseDomainApplyTime
+ val currentTime: Long = System.currentTimeMillis()
+ if (
+ force || lastUpdateTime == -1L || currentTime - lastUpdateTime >= SEARCH_BASE_URL_EXPIRY_MS
+ ) {
+ if (mSearchSettings.shouldUseGoogleCom()) {
+ setSearchBaseDomain(defaultBaseDomain)
+ } else {
+ checkSearchDomain()
+ }
+ }
+ }
+
+ /** @return the base url for searches. */
+ val searchBaseUrl: String?
+ get() =
+ mContext
+ ?.getResources()
+ ?.getString(
+ R.string.google_search_base_pattern,
+ searchDomain,
+ GoogleSearch.getLanguage(Locale.getDefault())
+ ) // This is required to deal with the case wherein getSearchDomain
+ // is called before checkSearchDomain returns a valid URL. This will
+ // happen *only* on the first run of the app when the "use google.com"
+ // option is unchecked. In other cases, the previously set domain (or
+ // the default) will be returned.
+ //
+ // We have no choice in this case but to use the default search domain.
+ /**
+ * @return the search domain. This is of the form "google.co.xx" or "google.com", used by UI code.
+ */
+ val searchDomain: String?
+ get() {
+ var domain: String? = mSearchSettings.searchBaseDomain
+ if (domain == null) {
+ if (DBG) {
+ Log.w(
+ TAG,
+ "Search base domain was null, last apply time=" +
+ mSearchSettings.searchBaseDomainApplyTime
+ )
+ }
+
+ // This is required to deal with the case wherein getSearchDomain
+ // is called before checkSearchDomain returns a valid URL. This will
+ // happen *only* on the first run of the app when the "use google.com"
+ // option is unchecked. In other cases, the previously set domain (or
+ // the default) will be returned.
+ //
+ // We have no choice in this case but to use the default search domain.
+ domain = defaultBaseDomain
+ }
+ if (domain?.startsWith(".") == true) {
+ if (DBG) Log.d(TAG, "Prepending www to $domain")
+ domain = "www$domain"
+ }
+ return domain
+ }
+
+ /**
+ * Issue a request to google.com/searchdomaincheck to retrieve the base URL for search requests.
+ */
+ private fun checkSearchDomain() {
+ val request = HttpHelper.GetRequest(DOMAIN_CHECK_URL)
+ scope.async {
+ if (DBG) Log.d(TAG, "Starting request to /searchdomaincheck")
+ var domain: String?
+ try {
+ domain = mHttpHelper[request]
+ } catch (e: Exception) {
+ if (DBG) Log.d(TAG, "Request to /searchdomaincheck failed : $e")
+ // Swallow any exceptions thrown by the HTTP helper, in
+ // this rare case, we just use the default URL.
+ domain = defaultBaseDomain
+ }
+ if (DBG) Log.d(TAG, "Request to /searchdomaincheck succeeded")
+ setSearchBaseDomain(domain)
+ }
+ }
+
+ private val defaultBaseDomain: String?
+ get() = mContext?.getResources()?.getString(R.string.default_search_domain)
+
+ private fun setSearchBaseDomain(domain: String?) {
+ if (DBG) Log.d(TAG, "Setting search domain to : $domain")
+ mSearchSettings.searchBaseDomain = domain
+ }
+
+ @Override
+ override fun onSharedPreferenceChanged(pref: SharedPreferences?, key: String?) {
+ // Listen for changes only to the SEARCH_BASE_URL preference.
+ if (DBG) Log.d(TAG, "Handling changed preference : $key")
+ if (SearchSettingsImpl.USE_GOOGLE_COM_PREF.equals(key)) {
+ maybeUpdateBaseUrlSetting(true)
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SearchBaseUrlHelper"
+ private const val DOMAIN_CHECK_URL = "https://www.google.com/searchdomaincheck?format=domain"
+ private const val SEARCH_BASE_URL_EXPIRY_MS = 24 * 3600 * 1000L
+ }
+
+ /**
+ * Note that this constructor will spawn a thread to issue a HTTP request if shouldUseGoogleCom is
+ * false.
+ */
+ init {
+ mHttpHelper = helper
+ mContext = context
+ mSearchSettings = searchSettings
+
+ // Note: This earlier used an inner class, but that causes issues
+ // because SharedPreferencesImpl uses a WeakHashMap< > and the listener
+ // will be GC'ed unless we keep a reference to it here.
+ prefs.registerOnSharedPreferenceChangeListener(this)
+ maybeUpdateBaseUrlSetting(false)
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/BaseSuggestionView.java b/src/com/android/quicksearchbox/ui/BaseSuggestionView.java
deleted file mode 100644
index ed7f74b..0000000
--- a/src/com/android/quicksearchbox/ui/BaseSuggestionView.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Suggestion;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-/**
- * Base class for suggestion views.
- */
-public abstract class BaseSuggestionView extends RelativeLayout implements SuggestionView {
-
- protected TextView mText1;
- protected TextView mText2;
- protected ImageView mIcon1;
- protected ImageView mIcon2;
- private long mSuggestionId;
- private SuggestionsAdapter<?> mAdapter;
-
- public BaseSuggestionView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public BaseSuggestionView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public BaseSuggestionView(Context context) {
- super(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mText1 = (TextView) findViewById(R.id.text1);
- mText2 = (TextView) findViewById(R.id.text2);
- mIcon1 = (ImageView) findViewById(R.id.icon1);
- mIcon2 = (ImageView) findViewById(R.id.icon2);
- }
-
- @Override
- public void bindAsSuggestion(Suggestion suggestion, String userQuery) {
- setOnClickListener(new ClickListener());
- }
-
- @Override
- public void bindAdapter(SuggestionsAdapter<?> adapter, long suggestionId) {
- mAdapter = adapter;
- mSuggestionId = suggestionId;
- }
-
- protected boolean isFromHistory(Suggestion suggestion) {
- return suggestion.isSuggestionShortcut() || suggestion.isHistorySuggestion();
- }
-
- /**
- * Sets the first text line.
- */
- protected void setText1(CharSequence text) {
- mText1.setText(text);
- }
-
- /**
- * Sets the second text line.
- */
- protected void setText2(CharSequence text) {
- mText2.setText(text);
- if (TextUtils.isEmpty(text)) {
- mText2.setVisibility(GONE);
- } else {
- mText2.setVisibility(VISIBLE);
- }
- }
-
- protected void onSuggestionClicked() {
- if (mAdapter != null) {
- mAdapter.onSuggestionClicked(mSuggestionId);
- }
- }
-
- protected void onSuggestionQueryRefineClicked() {
- if (mAdapter != null) {
- mAdapter.onSuggestionQueryRefineClicked(mSuggestionId);
- }
- }
-
- private class ClickListener implements OnClickListener {
- @Override
- public void onClick(View v) {
- onSuggestionClicked();
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/BaseSuggestionView.kt b/src/com/android/quicksearchbox/ui/BaseSuggestionView.kt
new file mode 100644
index 0000000..ec40f50
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/BaseSuggestionView.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Suggestion
+
+/** Base class for suggestion views. */
+abstract class BaseSuggestionView : RelativeLayout, SuggestionView {
+ @JvmField protected var mText1: TextView? = null
+ @JvmField protected var mText2: TextView? = null
+ @JvmField protected var mIcon1: ImageView? = null
+ @JvmField protected var mIcon2: ImageView? = null
+ private var mSuggestionId: Long = 0
+ private var mAdapter: SuggestionsAdapter<*>? = null
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context?) : super(context)
+
+ @Override
+ protected override fun onFinishInflate() {
+ super.onFinishInflate()
+ mText1 = findViewById(R.id.text1) as TextView?
+ mText2 = findViewById(R.id.text2) as TextView?
+ mIcon1 = findViewById(R.id.icon1) as ImageView?
+ mIcon2 = findViewById(R.id.icon2) as ImageView?
+ }
+
+ @Override
+ override fun bindAsSuggestion(suggestion: Suggestion?, userQuery: String?) {
+ setOnClickListener(ClickListener())
+ }
+
+ @Override
+ override fun bindAdapter(adapter: SuggestionsAdapter<*>?, position: Long) {
+ mAdapter = adapter
+ mSuggestionId = position
+ }
+
+ protected fun isFromHistory(suggestion: Suggestion?): Boolean {
+ return suggestion?.isSuggestionShortcut == true || suggestion?.isHistorySuggestion == true
+ }
+
+ /** Sets the first text line. */
+ protected fun setText1(text: CharSequence?) {
+ mText1?.setText(text)
+ }
+
+ /** Sets the second text line. */
+ protected fun setText2(text: CharSequence?) {
+ mText2?.setText(text)
+ if (TextUtils.isEmpty(text)) {
+ mText2?.setVisibility(GONE)
+ } else {
+ mText2?.setVisibility(VISIBLE)
+ }
+ }
+
+ protected fun onSuggestionClicked() {
+ if (mAdapter != null) {
+ mAdapter!!.onSuggestionClicked(mSuggestionId)
+ }
+ }
+
+ protected fun onSuggestionQueryRefineClicked() {
+ if (mAdapter != null) {
+ mAdapter!!.onSuggestionQueryRefineClicked(mSuggestionId)
+ }
+ }
+
+ private inner class ClickListener : OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ onSuggestionClicked()
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.java b/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.java
deleted file mode 100644
index 9427024..0000000
--- a/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-/**
- * Suggestions view that displays suggestions clustered by corpus type.
- */
-public class ClusteredSuggestionsView extends ExpandableListView
- implements SuggestionsListView<ExpandableListAdapter> {
-
- SuggestionsAdapter<ExpandableListAdapter> mSuggestionsAdapter;
-
- public ClusteredSuggestionsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public void setSuggestionsAdapter(SuggestionsAdapter<ExpandableListAdapter> adapter) {
- mSuggestionsAdapter = adapter;
- super.setAdapter(adapter == null ? null : adapter.getListAdapter());
- }
-
- public SuggestionsAdapter<ExpandableListAdapter> getSuggestionsAdapter() {
- return mSuggestionsAdapter;
- }
-
- public void setLimitSuggestionsToViewHeight(boolean limit) {
- // not supported
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- setItemsCanFocus(false);
- setOnGroupClickListener(new OnGroupClickListener(){
- public boolean onGroupClick(
- ExpandableListView parent, View v, int groupPosition, long id) {
- // disable collapsing / expanding
- return true;
- }});
- }
-
- public void expandAll() {
- if (mSuggestionsAdapter != null) {
- ExpandableListAdapter adapter = mSuggestionsAdapter.getListAdapter();
- for (int i = 0; i < adapter.getGroupCount(); ++i) {
- expandGroup(i);
- }
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.kt b/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.kt
new file mode 100644
index 0000000..b870fa3
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/ClusteredSuggestionsView.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ExpandableListAdapter
+import android.widget.ExpandableListView
+
+/** Suggestions view that displays suggestions clustered by corpus type. */
+class ClusteredSuggestionsView(context: Context?, attrs: AttributeSet?) :
+ ExpandableListView(context, attrs), SuggestionsListView<ExpandableListAdapter?> {
+
+ @JvmField var mSuggestionsAdapter: SuggestionsAdapter<ExpandableListAdapter?>? = null
+
+ override fun setSuggestionsAdapter(adapter: SuggestionsAdapter<ExpandableListAdapter?>?) {
+ mSuggestionsAdapter = adapter
+ super.setAdapter(adapter?.listAdapter)
+ }
+
+ override fun getSuggestionsAdapter(): SuggestionsAdapter<ExpandableListAdapter?>? {
+ return mSuggestionsAdapter
+ }
+
+ // TODO: this function does not appear to be used currently and remains unimplemented
+ override fun getSelectedItemId(): Long {
+ return 0
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun setLimitSuggestionsToViewHeight(limit: Boolean) {
+ // not supported
+ }
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setItemsCanFocus(false)
+ setOnGroupClickListener(
+ object : OnGroupClickListener {
+ override fun onGroupClick(
+ parent: ExpandableListView?,
+ v: View?,
+ groupPosition: Int,
+ id: Long
+ ): Boolean {
+ // disable collapsing / expanding
+ return true
+ }
+ }
+ )
+ }
+
+ fun expandAll() {
+ if (mSuggestionsAdapter != null) {
+ val adapter: ExpandableListAdapter? = mSuggestionsAdapter?.listAdapter
+ for (i in 0 until adapter!!.getGroupCount()) {
+ expandGroup(i)
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/ContactBadge.java b/src/com/android/quicksearchbox/ui/ContactBadge.java
deleted file mode 100644
index 15b8320..0000000
--- a/src/com/android/quicksearchbox/ui/ContactBadge.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.QuickContactBadge;
-
-/**
- * A {@link QuickContactBadge} that allows setting a click listener.
- * The base class may use {@link View#setOnClickListener} internally,
- * so this class adds a separate click listener field.
- */
-public class ContactBadge extends QuickContactBadge {
-
- private View.OnClickListener mExtraOnClickListener;
-
- public ContactBadge(Context context) {
- super(context);
- }
-
- public ContactBadge(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ContactBadge(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void onClick(View v) {
- super.onClick(v);
- if (mExtraOnClickListener != null) {
- mExtraOnClickListener.onClick(v);
- }
- }
-
- public void setExtraOnClickListener(View.OnClickListener extraOnClickListener) {
- mExtraOnClickListener = extraOnClickListener;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/ContactBadge.kt b/src/com/android/quicksearchbox/ui/ContactBadge.kt
new file mode 100644
index 0000000..9b87cc2
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/ContactBadge.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.QuickContactBadge
+
+/**
+ * A [QuickContactBadge] that allows setting a click listener. The base class may use
+ * [View.setOnClickListener] internally, so this class adds a separate click listener field.
+ */
+class ContactBadge : QuickContactBadge {
+ private var mExtraOnClickListener: View.OnClickListener? = null
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ @Override
+ override fun onClick(v: View?) {
+ super.onClick(v)
+ if (mExtraOnClickListener != null) {
+ mExtraOnClickListener?.onClick(v)
+ }
+ }
+
+ fun setExtraOnClickListener(extraOnClickListener: View.OnClickListener?) {
+ mExtraOnClickListener = extraOnClickListener
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/CorpusView.java b/src/com/android/quicksearchbox/ui/CorpusView.java
deleted file mode 100644
index 23982d1..0000000
--- a/src/com/android/quicksearchbox/ui/CorpusView.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.R;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.ViewDebug;
-import android.widget.Checkable;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-
-/**
- * A corpus in the corpus selection list.
- */
-public class CorpusView extends RelativeLayout implements Checkable {
-
- private ImageView mIcon;
- private TextView mLabel;
- private boolean mChecked;
-
- private static final int[] CHECKED_STATE_SET = {
- android.R.attr.state_checked
- };
-
- public CorpusView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CorpusView(Context context) {
- super(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIcon = (ImageView) findViewById(R.id.source_icon);
- mLabel = (TextView) findViewById(R.id.source_label);
- }
-
- public void setLabel(CharSequence label) {
- mLabel.setText(label);
- }
-
- public void setIcon(Drawable icon) {
- mIcon.setImageDrawable(icon);
- }
-
- @Override
- @ViewDebug.ExportedProperty
- public boolean isChecked() {
- return mChecked;
- }
-
- @Override
- public void setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- refreshDrawableState();
- }
- }
-
- @Override
- public void toggle() {
- setChecked(!mChecked);
- }
-
- @Override
- protected int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- if (isChecked()) {
- mergeDrawableStates(drawableState, CHECKED_STATE_SET);
- }
- return drawableState;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/CorpusView.kt b/src/com/android/quicksearchbox/ui/CorpusView.kt
new file mode 100644
index 0000000..96eab98
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/CorpusView.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.ViewDebug
+import android.widget.Checkable
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import com.android.quicksearchbox.R
+
+/** A corpus in the corpus selection list. */
+class CorpusView : RelativeLayout, Checkable {
+ private var mIcon: ImageView? = null
+ private var mLabel: TextView? = null
+ private var mChecked = false
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
+ constructor(context: Context?) : super(context) {}
+
+ @Override
+ protected override fun onFinishInflate() {
+ super.onFinishInflate()
+ mIcon = findViewById(R.id.source_icon) as ImageView?
+ mLabel = findViewById(R.id.source_label) as TextView?
+ }
+
+ fun setLabel(label: CharSequence?) {
+ mLabel?.setText(label)
+ }
+
+ fun setIcon(icon: Drawable?) {
+ mIcon?.setImageDrawable(icon)
+ }
+
+ @Override
+ @ViewDebug.ExportedProperty
+ override fun isChecked(): Boolean {
+ return mChecked
+ }
+
+ @Override
+ override fun setChecked(checked: Boolean) {
+ if (mChecked != checked) {
+ mChecked = checked
+ refreshDrawableState()
+ }
+ }
+
+ @Override
+ override fun toggle() {
+ isChecked = !mChecked
+ }
+
+ @Override
+ protected override fun onCreateDrawableState(extraSpace: Int): IntArray {
+ val drawableState: IntArray = super.onCreateDrawableState(extraSpace + 1)
+ if (isChecked) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET)
+ }
+ return drawableState
+ }
+
+ companion object {
+ private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
deleted file mode 100644
index c946568..0000000
--- a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.util.Consumer;
-import com.android.quicksearchbox.util.NowOrLater;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.text.Html;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * View for the items in the suggestions list. This includes promoted suggestions,
- * sources, and suggestions under each source.
- */
-public class DefaultSuggestionView extends BaseSuggestionView {
-
- private static final boolean DBG = false;
-
- private static final String VIEW_ID = "default";
-
- private final String TAG = "QSB.DefaultSuggestionView";
-
- private AsyncIcon mAsyncIcon1;
- private AsyncIcon mAsyncIcon2;
-
- public DefaultSuggestionView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public DefaultSuggestionView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DefaultSuggestionView(Context context) {
- super(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mText1 = (TextView) findViewById(R.id.text1);
- mText2 = (TextView) findViewById(R.id.text2);
- mAsyncIcon1 = new AsyncIcon(mIcon1) {
- // override default icon (when no other available) with default source icon
- @Override
- protected String getFallbackIconId(Source source) {
- return source.getSourceIconUri().toString();
- }
- @Override
- protected Drawable getFallbackIcon(Source source) {
- return source.getSourceIcon();
- }
- };
- mAsyncIcon2 = new AsyncIcon(mIcon2);
- }
-
- @Override
- public void bindAsSuggestion(Suggestion suggestion, String userQuery) {
- super.bindAsSuggestion(suggestion, userQuery);
-
- CharSequence text1 = formatText(suggestion.getSuggestionText1(), suggestion);
- CharSequence text2 = suggestion.getSuggestionText2Url();
- if (text2 != null) {
- text2 = formatUrl(text2);
- } else {
- text2 = formatText(suggestion.getSuggestionText2(), suggestion);
- }
- // If there is no text for the second line, allow the first line to be up to two lines
- if (TextUtils.isEmpty(text2)) {
- mText1.setSingleLine(false);
- mText1.setMaxLines(2);
- mText1.setEllipsize(TextUtils.TruncateAt.START);
- } else {
- mText1.setSingleLine(true);
- mText1.setMaxLines(1);
- mText1.setEllipsize(TextUtils.TruncateAt.MIDDLE);
- }
- setText1(text1);
- setText2(text2);
- mAsyncIcon1.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon1());
- mAsyncIcon2.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon2());
-
- if (DBG) {
- Log.d(TAG, "bindAsSuggestion(), text1=" + text1 + ",text2=" + text2 + ",q='" +
- userQuery + ",fromHistory=" + isFromHistory(suggestion));
- }
- }
-
- private CharSequence formatUrl(CharSequence url) {
- SpannableString text = new SpannableString(url);
- ColorStateList colors = getResources().getColorStateList(R.color.url_text);
- text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
- 0, url.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return text;
- }
-
- private CharSequence formatText(String str, Suggestion suggestion) {
- boolean isHtml = "html".equals(suggestion.getSuggestionFormat());
- if (isHtml && looksLikeHtml(str)) {
- return Html.fromHtml(str);
- } else {
- return str;
- }
- }
-
- private boolean looksLikeHtml(String str) {
- if (TextUtils.isEmpty(str)) return false;
- for (int i = str.length() - 1; i >= 0; i--) {
- char c = str.charAt(i);
- if (c == '>' || c == '&') return true;
- }
- return false;
- }
-
- /**
- * Sets the drawable in an image view, makes sure the view is only visible if there
- * is a drawable.
- */
- private static void setViewDrawable(ImageView v, Drawable drawable) {
- // Set the icon even if the drawable is null, since we need to clear any
- // previous icon.
- v.setImageDrawable(drawable);
-
- if (drawable == null) {
- v.setVisibility(View.GONE);
- } else {
- v.setVisibility(View.VISIBLE);
-
- // This is a hack to get any animated drawables (like a 'working' spinner)
- // to animate. You have to setVisible true on an AnimationDrawable to get
- // it to start animating, but it must first have been false or else the
- // call to setVisible will be ineffective. We need to clear up the story
- // about animated drawables in the future, see http://b/1878430.
- drawable.setVisible(false, false);
- drawable.setVisible(true, false);
- }
- }
-
- private class AsyncIcon {
- private final ImageView mView;
- private String mCurrentId;
- private String mWantedId;
-
- public AsyncIcon(ImageView view) {
- mView = view;
- }
-
- public void set(final Source source, final String sourceIconId) {
- if (sourceIconId != null) {
- // The iconId can just be a package-relative resource ID, which may overlap with
- // other packages. Make sure it's globally unique.
- Uri iconUri = source.getIconUri(sourceIconId);
- final String uniqueIconId = iconUri == null ? null : iconUri.toString();
- mWantedId = uniqueIconId;
- if (!TextUtils.equals(mWantedId, mCurrentId)) {
- if (DBG) Log.d(TAG, "getting icon Id=" + uniqueIconId);
- NowOrLater<Drawable> icon = source.getIcon(sourceIconId);
- if (icon.haveNow()) {
- if (DBG) Log.d(TAG, "getIcon ready now");
- handleNewDrawable(icon.getNow(), uniqueIconId, source);
- } else {
- // make sure old icon is not visible while new one is loaded
- if (DBG) Log.d(TAG , "getIcon getting later");
- clearDrawable();
- icon.getLater(new Consumer<Drawable>(){
- @Override
- public boolean consume(Drawable icon) {
- if (DBG) {
- Log.d(TAG, "IconConsumer.consume got id " + uniqueIconId +
- " want id " + mWantedId);
- }
- // ensure we have not been re-bound since the request was made.
- if (TextUtils.equals(uniqueIconId, mWantedId)) {
- handleNewDrawable(icon, uniqueIconId, source);
- return true;
- }
- return false;
- }});
- }
- }
- } else {
- mWantedId = null;
- handleNewDrawable(null, null, source);
- }
- }
-
- private void handleNewDrawable(Drawable icon, String id, Source source) {
- if (icon == null) {
- mWantedId = getFallbackIconId(source);
- if (TextUtils.equals(mWantedId, mCurrentId)) {
- return;
- }
- icon = getFallbackIcon(source);
- }
- setDrawable(icon, id);
- }
-
- private void setDrawable(Drawable icon, String id) {
- mCurrentId = id;
- setViewDrawable(mView, icon);
- }
-
- private void clearDrawable() {
- mCurrentId = null;
- mView.setImageDrawable(null);
- }
-
- protected String getFallbackIconId(Source source) {
- return null;
- }
-
- protected Drawable getFallbackIcon(Source source) {
- return null;
- }
-
- }
-
- public static class Factory extends SuggestionViewInflater {
- public Factory(Context context) {
- super(VIEW_ID, DefaultSuggestionView.class, R.layout.suggestion, context);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.kt b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.kt
new file mode 100644
index 0000000..3134d72
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.text.Html
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.TextUtils
+import android.text.style.TextAppearanceSpan
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Source
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.util.Consumer
+import com.android.quicksearchbox.util.NowOrLater
+
+/**
+ * View for the items in the suggestions list. This includes promoted suggestions, sources, and
+ * suggestions under each source.
+ */
+class DefaultSuggestionView : BaseSuggestionView {
+ private val TAG = "QSB.DefaultSuggestionView"
+ private var mAsyncIcon1: DefaultSuggestionView.AsyncIcon? = null
+ private var mAsyncIcon2: DefaultSuggestionView.AsyncIcon? = null
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context?) : super(context)
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ mText1 = findViewById(R.id.text1) as TextView
+ mText2 = findViewById(R.id.text2) as TextView
+ mAsyncIcon1 =
+ object : AsyncIcon(mIcon1) {
+ // override default icon (when no other available) with default source icon
+ @Override
+ override fun getFallbackIconId(source: Source?): String {
+ return source?.sourceIconUri.toString()
+ }
+
+ @Override
+ override fun getFallbackIcon(source: Source?): Drawable? {
+ return source?.sourceIcon
+ }
+ }
+ mAsyncIcon2 = AsyncIcon(mIcon2)
+ }
+
+ @Override
+ override fun bindAsSuggestion(suggestion: Suggestion?, userQuery: String?) {
+ super.bindAsSuggestion(suggestion, userQuery)
+ val text1 = formatText(suggestion?.suggestionText1, suggestion)
+ var text2: CharSequence = suggestion?.suggestionText2Url as CharSequence
+ text2 = formatUrl(text2)
+ // If there is no text for the second line, allow the first line to be up to two lines
+ if (TextUtils.isEmpty(text2)) {
+ mText1?.setSingleLine(false)
+ mText1?.setMaxLines(2)
+ mText1?.setEllipsize(TextUtils.TruncateAt.START)
+ } else {
+ mText1?.setSingleLine(true)
+ mText1?.setMaxLines(1)
+ mText1?.setEllipsize(TextUtils.TruncateAt.MIDDLE)
+ }
+ setText1(text1)
+ setText2(text2)
+ mAsyncIcon1?.set(suggestion.suggestionSource, suggestion.suggestionIcon1)
+ mAsyncIcon2?.set(suggestion.suggestionSource, suggestion.suggestionIcon2)
+ if (DBG) {
+ Log.d(
+ TAG,
+ "bindAsSuggestion(), text1=" +
+ text1 +
+ ",text2=" +
+ text2 +
+ ",q='" +
+ userQuery +
+ ",fromHistory=" +
+ isFromHistory(suggestion)
+ )
+ }
+ }
+
+ private fun formatUrl(url: CharSequence): CharSequence {
+ val text = SpannableString(url)
+ val colors: ColorStateList = getResources().getColorStateList(R.color.url_text, null)
+ text.setSpan(
+ TextAppearanceSpan(null, 0, 0, colors, null),
+ 0,
+ url.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ return text
+ }
+
+ private fun formatText(str: String?, suggestion: Suggestion?): CharSequence {
+ val isHtml = "html" == suggestion?.suggestionFormat
+ return if (isHtml && looksLikeHtml(str)) {
+ Html.fromHtml(str, Html.FROM_HTML_MODE_LEGACY)
+ } else {
+ str as CharSequence
+ }
+ }
+
+ private fun looksLikeHtml(str: String?): Boolean {
+ if (TextUtils.isEmpty(str)) return false
+ for (i in str!!.length - 1 downTo 0) {
+ val c: Char = str[i]
+ if (c == '>' || c == '&') return true
+ }
+ return false
+ }
+
+ private open inner class AsyncIcon(view: ImageView?) {
+ private val mView: ImageView?
+ private var mCurrentId: String? = null
+ private var mWantedId: String? = null
+
+ operator fun set(source: Source?, sourceIconId: String?) {
+ if (sourceIconId != null) {
+ // The iconId can just be a package-relative resource ID, which may overlap with
+ // other packages. Make sure it's globally unique.
+ val iconUri: Uri? = source?.getIconUri(sourceIconId)
+ val uniqueIconId: String? = if (iconUri == null) null else iconUri.toString()
+ mWantedId = uniqueIconId
+ if (!TextUtils.equals(mWantedId, mCurrentId)) {
+ if (DBG) Log.d(TAG, "getting icon Id=$uniqueIconId")
+ val icon: NowOrLater<Drawable?>? = source?.getIcon(sourceIconId)
+ if (icon!!.haveNow()) {
+ if (DBG) Log.d(TAG, "getIcon ready now")
+ handleNewDrawable(icon.now, uniqueIconId, source)
+ } else {
+ // make sure old icon is not visible while new one is loaded
+ if (DBG) Log.d(TAG, "getIcon getting later")
+ clearDrawable()
+ icon.getLater(
+ object : Consumer<Drawable?> {
+ @Override
+ override fun consume(value: Drawable?): Boolean {
+ if (DBG) {
+ Log.d(TAG, "IconConsumer.consume got id $uniqueIconId want id $mWantedId")
+ }
+ // ensure we have not been re-bound since the request was made.
+ if (TextUtils.equals(uniqueIconId, mWantedId)) {
+ handleNewDrawable(value, uniqueIconId, source)
+ return true
+ }
+ return false
+ }
+ }
+ )
+ }
+ }
+ } else {
+ mWantedId = null
+ handleNewDrawable(null, null, source)
+ }
+ }
+
+ private fun handleNewDrawable(icon: Drawable?, id: String?, source: Source?) {
+ var mIcon: Drawable? = icon
+ if (mIcon == null) {
+ mWantedId = getFallbackIconId(source)
+ if (TextUtils.equals(mWantedId, mCurrentId)) {
+ return
+ }
+ mIcon = getFallbackIcon(source)
+ }
+ setDrawable(mIcon, id)
+ }
+
+ private fun setDrawable(icon: Drawable?, id: String?) {
+ mCurrentId = id
+ setViewDrawable(mView, icon)
+ }
+
+ private fun clearDrawable() {
+ mCurrentId = null
+ mView?.setImageDrawable(null)
+ }
+
+ protected open fun getFallbackIconId(source: Source?): String? {
+ return null
+ }
+
+ protected open fun getFallbackIcon(source: Source?): Drawable? {
+ return null
+ }
+
+ init {
+ mView = view
+ }
+ }
+
+ class Factory(context: Context?) :
+ SuggestionViewInflater(
+ VIEW_ID,
+ DefaultSuggestionView::class.java,
+ R.layout.suggestion,
+ context
+ )
+
+ companion object {
+ private const val DBG = false
+ private const val VIEW_ID = "default"
+
+ /**
+ * Sets the drawable in an image view, makes sure the view is only visible if there is a
+ * drawable.
+ */
+ private fun setViewDrawable(v: ImageView?, drawable: Drawable?) {
+ // Set the icon even if the drawable is null, since we need to clear any
+ // previous icon.
+ v?.setImageDrawable(drawable)
+ if (drawable == null) {
+ v?.setVisibility(View.GONE)
+ } else {
+ v?.setVisibility(View.VISIBLE)
+
+ // This is a hack to get any animated drawables (like a 'working' spinner)
+ // to animate. You have to setVisible true on an AnimationDrawable to get
+ // it to start animating, but it must first have been false or else the
+ // call to setVisible will be ineffective. We need to clear up the story
+ // about animated drawables in the future, see http://b/1878430.
+ drawable.setVisible(false, false)
+ drawable.setVisible(true, false)
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.java b/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.java
deleted file mode 100644
index ed4625f..0000000
--- a/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.SuggestionCursor;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-
-/**
- * Suggestion view factory for Google suggestions.
- */
-public class DefaultSuggestionViewFactory implements SuggestionViewFactory {
-
- private final LinkedList<SuggestionViewFactory> mFactories
- = new LinkedList<SuggestionViewFactory>();
- private final SuggestionViewFactory mDefaultFactory;
- private HashSet<String> mViewTypes;
-
- public DefaultSuggestionViewFactory(Context context) {
- mDefaultFactory = new DefaultSuggestionView.Factory(context);
- addFactory(new WebSearchSuggestionView.Factory(context));
- }
-
- /**
- * Must only be called from the constructor
- */
- protected final void addFactory(SuggestionViewFactory factory) {
- mFactories.addFirst(factory);
- }
-
- @Override
- public Collection<String> getSuggestionViewTypes() {
- if (mViewTypes == null) {
- mViewTypes = new HashSet<String>();
- mViewTypes.addAll(mDefaultFactory.getSuggestionViewTypes());
- for (SuggestionViewFactory factory : mFactories) {
- mViewTypes.addAll(factory.getSuggestionViewTypes());
- }
- }
- return mViewTypes;
- }
-
- @Override
- public View getView(SuggestionCursor suggestion, String userQuery,
- View convertView, ViewGroup parent) {
- for (SuggestionViewFactory factory : mFactories) {
- if (factory.canCreateView(suggestion)) {
- return factory.getView(suggestion, userQuery, convertView, parent);
- }
- }
- return mDefaultFactory.getView(suggestion, userQuery, convertView, parent);
- }
-
- @Override
- public String getViewType(Suggestion suggestion) {
- for (SuggestionViewFactory factory : mFactories) {
- if (factory.canCreateView(suggestion)) {
- return factory.getViewType(suggestion);
- }
- }
- return mDefaultFactory.getViewType(suggestion);
- }
-
- @Override
- public boolean canCreateView(Suggestion suggestion) {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.kt b/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.kt
new file mode 100644
index 0000000..5559f13
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/DefaultSuggestionViewFactory.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+import java.util.LinkedList
+
+/** Suggestion view factory for Google suggestions. */
+class DefaultSuggestionViewFactory(context: Context?) : SuggestionViewFactory {
+ private val mFactories: LinkedList<SuggestionViewFactory> = LinkedList<SuggestionViewFactory>()
+ private val mDefaultFactory: SuggestionViewFactory
+ private var mViewTypes: HashSet<String>? = null
+
+ /** Must only be called from the constructor */
+ protected fun addFactory(factory: SuggestionViewFactory?) {
+ mFactories.addFirst(factory)
+ }
+
+ @get:Override
+ override val suggestionViewTypes: Collection<String>
+ get() {
+ if (mViewTypes == null) {
+ mViewTypes = hashSetOf()
+ mViewTypes?.addAll(mDefaultFactory.suggestionViewTypes)
+ for (factory in mFactories) {
+ mViewTypes?.addAll(factory.suggestionViewTypes)
+ }
+ }
+ return mViewTypes as Collection<String>
+ }
+
+ @Override
+ override fun getView(
+ suggestion: SuggestionCursor?,
+ userQuery: String?,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View? {
+ for (factory in mFactories) {
+ if (factory.canCreateView(suggestion)) {
+ return factory.getView(suggestion, userQuery, convertView, parent)
+ }
+ }
+ return mDefaultFactory.getView(suggestion, userQuery, convertView, parent)
+ }
+
+ @Override
+ override fun getViewType(suggestion: Suggestion?): String {
+ for (factory in mFactories) {
+ if (factory.canCreateView(suggestion)) {
+ return factory.getViewType(suggestion)!!
+ }
+ }
+ return mDefaultFactory.getViewType(suggestion)!!
+ }
+
+ @Override
+ override fun canCreateView(suggestion: Suggestion?): Boolean {
+ return true
+ }
+
+ init {
+ mDefaultFactory = DefaultSuggestionView.Factory(context)
+ addFactory(WebSearchSuggestionView.Factory(context))
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java
deleted file mode 100644
index 6b7d47e..0000000
--- a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
-import com.android.quicksearchbox.Suggestions;
-
-import android.database.DataSetObserver;
-import android.util.Log;
-import android.view.View.OnFocusChangeListener;
-
-/**
- * A {@link SuggestionsListAdapter} that doesn't expose the new suggestions
- * until there are some results to show.
- */
-public class DelayingSuggestionsAdapter<A> implements SuggestionsAdapter<A> {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.DelayingSuggestionsAdapter";
-
- private DataSetObserver mPendingDataSetObserver;
-
- private Suggestions mPendingSuggestions;
-
- private final SuggestionsAdapterBase<A> mDelayedAdapter;
-
- public DelayingSuggestionsAdapter(SuggestionsAdapterBase<A> delayed) {
- mDelayedAdapter = delayed;
- }
-
- public void close() {
- setPendingSuggestions(null);
- mDelayedAdapter.close();
- }
-
- @Override
- public void setSuggestions(Suggestions suggestions) {
- if (suggestions == null) {
- mDelayedAdapter.setSuggestions(null);
- setPendingSuggestions(null);
- return;
- }
- if (shouldPublish(suggestions)) {
- if (DBG) Log.d(TAG, "Publishing suggestions immediately: " + suggestions);
- mDelayedAdapter.setSuggestions(suggestions);
- // Clear any old pending suggestions.
- setPendingSuggestions(null);
- } else {
- if (DBG) Log.d(TAG, "Delaying suggestions publishing: " + suggestions);
- setPendingSuggestions(suggestions);
- }
- }
-
- /**
- * Gets whether the given suggestions are non-empty for the selected source.
- */
- private boolean shouldPublish(Suggestions suggestions) {
- if (suggestions.isDone()) return true;
- SuggestionCursor cursor = suggestions.getResult();
- if (cursor != null && cursor.getCount() > 0) {
- return true;
- }
- return false;
- }
-
- private void setPendingSuggestions(Suggestions suggestions) {
- if (mPendingSuggestions == suggestions) {
- return;
- }
- if (mDelayedAdapter.isClosed()) {
- if (suggestions != null) {
- suggestions.release();
- }
- return;
- }
- if (mPendingDataSetObserver == null) {
- mPendingDataSetObserver = new PendingSuggestionsObserver();
- }
- if (mPendingSuggestions != null) {
- mPendingSuggestions.unregisterDataSetObserver(mPendingDataSetObserver);
- // Close old suggestions, but only if they are not also the current
- // suggestions.
- if (mPendingSuggestions != getSuggestions()) {
- mPendingSuggestions.release();
- }
- }
- mPendingSuggestions = suggestions;
- if (mPendingSuggestions != null) {
- mPendingSuggestions.registerDataSetObserver(mPendingDataSetObserver);
- }
- }
-
- protected void onPendingSuggestionsChanged() {
- if (DBG) {
- Log.d(TAG, "onPendingSuggestionsChanged(), mPendingSuggestions="
- + mPendingSuggestions);
- }
- if (shouldPublish(mPendingSuggestions)) {
- if (DBG) Log.d(TAG, "Suggestions now available, publishing: " + mPendingSuggestions);
- mDelayedAdapter.setSuggestions(mPendingSuggestions);
- // The suggestions are no longer pending.
- setPendingSuggestions(null);
- }
- }
-
- private class PendingSuggestionsObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- onPendingSuggestionsChanged();
- }
- }
-
- @Override
- public A getListAdapter() {
- return mDelayedAdapter.getListAdapter();
- }
-
- public SuggestionCursor getCurrentPromotedSuggestions() {
- return mDelayedAdapter.getCurrentSuggestions();
- }
-
- @Override
- public Suggestions getSuggestions() {
- return mDelayedAdapter.getSuggestions();
- }
-
- @Override
- public SuggestionPosition getSuggestion(long suggestionId) {
- return mDelayedAdapter.getSuggestion(suggestionId);
- }
-
- @Override
- public void onSuggestionClicked(long suggestionId) {
- mDelayedAdapter.onSuggestionClicked(suggestionId);
- }
-
- @Override
- public void onSuggestionQueryRefineClicked(long suggestionId) {
- mDelayedAdapter.onSuggestionQueryRefineClicked(suggestionId);
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener l) {
- mDelayedAdapter.setOnFocusChangeListener(l);
- }
-
- @Override
- public void setSuggestionClickListener(SuggestionClickListener listener) {
- mDelayedAdapter.setSuggestionClickListener(listener);
- }
-
- @Override
- public boolean isEmpty() {
- return mDelayedAdapter.isEmpty();
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt
new file mode 100644
index 0000000..a65a5da
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.database.DataSetObserver
+import android.util.Log
+import android.view.View.OnFocusChangeListener
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+import com.android.quicksearchbox.Suggestions
+
+/**
+ * A [SuggestionsListAdapter] that doesn't expose the new suggestions until there are some results
+ * to show.
+ */
+class DelayingSuggestionsAdapter<A>(private val mDelayedAdapter: SuggestionsAdapterBase<A>) :
+ SuggestionsAdapter<A> {
+ private var mPendingDataSetObserver: DataSetObserver? = null
+ private var mPendingSuggestions: Suggestions? = null
+ fun close() {
+ setPendingSuggestions(null)
+ mDelayedAdapter.close()
+ }
+
+ /** Gets whether the given suggestions are non-empty for the selected source. */
+ private fun shouldPublish(suggestions: Suggestions?): Boolean {
+ if (suggestions!!.isDone) return true
+ val cursor: SuggestionCursor? = suggestions.getResult()
+ return cursor != null && cursor.count > 0
+ }
+
+ private fun setPendingSuggestions(suggestions: Suggestions?) {
+ if (mPendingSuggestions === suggestions) {
+ return
+ }
+ if (mDelayedAdapter.isClosed) {
+ suggestions?.release()
+ return
+ }
+ if (mPendingDataSetObserver == null) {
+ mPendingDataSetObserver = PendingSuggestionsObserver()
+ }
+ if (mPendingSuggestions != null) {
+ mPendingSuggestions!!.unregisterDataSetObserver(mPendingDataSetObserver)
+ // Close old suggestions, but only if they are not also the current
+ // suggestions.
+ if (mPendingSuggestions !== this.suggestions) {
+ mPendingSuggestions!!.release()
+ }
+ }
+ mPendingSuggestions = suggestions
+ if (mPendingSuggestions != null) {
+ mPendingSuggestions!!.registerDataSetObserver(mPendingDataSetObserver)
+ }
+ }
+
+ protected fun onPendingSuggestionsChanged() {
+ if (DBG) Log.d(TAG, "onPendingSuggestionsChanged(), mPendingSuggestions=" + mPendingSuggestions)
+ if (shouldPublish(mPendingSuggestions)) {
+ if (DBG) Log.d(TAG, "Suggestions now available, publishing: $mPendingSuggestions")
+ mDelayedAdapter.suggestions = mPendingSuggestions
+ // The suggestions are no longer pending.
+ setPendingSuggestions(null)
+ }
+ }
+
+ private inner class PendingSuggestionsObserver : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ onPendingSuggestionsChanged()
+ }
+ }
+
+ @get:Override
+ override val listAdapter: A
+ get() = mDelayedAdapter.listAdapter
+ val currentPromotedSuggestions: SuggestionCursor?
+ get() = mDelayedAdapter.currentSuggestions
+
+ // Clear any old pending suggestions.
+ @get:Override
+ @set:Override
+ override var suggestions: Suggestions?
+ get() = mDelayedAdapter.suggestions
+ set(suggestions) {
+ if (suggestions == null) {
+ mDelayedAdapter.suggestions = null
+ setPendingSuggestions(null)
+ return
+ }
+ if (shouldPublish(suggestions)) {
+ if (DBG) Log.d(TAG, "Publishing suggestions immediately: $suggestions")
+ mDelayedAdapter.suggestions = suggestions
+ // Clear any old pending suggestions.
+ setPendingSuggestions(null)
+ } else {
+ if (DBG) Log.d(TAG, "Delaying suggestions publishing: $suggestions")
+ setPendingSuggestions(suggestions)
+ }
+ }
+
+ @Override
+ override fun getSuggestion(suggestionId: Long): SuggestionPosition? {
+ return mDelayedAdapter.getSuggestion(suggestionId)
+ }
+
+ @Override
+ override fun onSuggestionClicked(suggestionId: Long) {
+ mDelayedAdapter.onSuggestionClicked(suggestionId)
+ }
+
+ @Override
+ override fun onSuggestionQueryRefineClicked(suggestionId: Long) {
+ mDelayedAdapter.onSuggestionQueryRefineClicked(suggestionId)
+ }
+
+ @Override
+ override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
+ mDelayedAdapter.setOnFocusChangeListener(l)
+ }
+
+ @Override
+ override fun setSuggestionClickListener(listener: SuggestionClickListener?) {
+ mDelayedAdapter.setSuggestionClickListener(listener)
+ }
+
+ @get:Override
+ override val isEmpty: Boolean
+ get() = mDelayedAdapter.isEmpty
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.DelayingSuggestionsAdapter"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/QueryTextView.java b/src/com/android/quicksearchbox/ui/QueryTextView.java
deleted file mode 100644
index 2531204..0000000
--- a/src/com/android/quicksearchbox/ui/QueryTextView.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-/**
- * The query text field.
- */
-public class QueryTextView extends EditText {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.QueryTextView";
-
- private CommitCompletionListener mCommitCompletionListener;
-
- public QueryTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public QueryTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public QueryTextView(Context context) {
- super(context);
- }
-
- /**
- * Sets the text selection in the query text view.
- *
- * @param selectAll If {@code true}, selects the entire query.
- * If {@false}, no characters are selected, and the cursor is placed
- * at the end of the query.
- */
- public void setTextSelection(boolean selectAll) {
- if (selectAll) {
- selectAll();
- } else {
- setSelection(length());
- }
- }
-
- protected void replaceText(CharSequence text) {
- clearComposingText();
- setText(text);
- setTextSelection(false);
- }
-
- public void setCommitCompletionListener(CommitCompletionListener listener) {
- mCommitCompletionListener = listener;
- }
-
- private InputMethodManager getInputMethodManager() {
- return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- }
-
- public void showInputMethod() {
- InputMethodManager imm = getInputMethodManager();
- if (imm != null) {
- imm.showSoftInput(this, 0);
- }
- }
-
- public void hideInputMethod() {
- InputMethodManager imm = getInputMethodManager();
- if (imm != null) {
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
-
- @Override
- public void onCommitCompletion(CompletionInfo completion) {
- if (DBG) Log.d(TAG, "onCommitCompletion(" + completion + ")");
- hideInputMethod();
- replaceText(completion.getText());
- if (mCommitCompletionListener != null) {
- mCommitCompletionListener.onCommitCompletion(completion.getPosition());
- }
- }
-
- public interface CommitCompletionListener {
- void onCommitCompletion(int position);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/QueryTextView.kt b/src/com/android/quicksearchbox/ui/QueryTextView.kt
new file mode 100644
index 0000000..e4b82d2
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/QueryTextView.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.inputmethod.CompletionInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+
+/** The query text field. */
+class QueryTextView : EditText {
+ private var mCommitCompletionListener: CommitCompletionListener? = null
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context?) : super(context)
+
+ /**
+ * Sets the text selection in the query text view.
+ *
+ * @param selectAll If `true`, selects the entire query. If {@false}, no characters are selected,
+ * and the cursor is placed at the end of the query.
+ */
+ fun setTextSelection(selectAll: Boolean) {
+ if (selectAll) {
+ selectAll()
+ } else {
+ setSelection(length())
+ }
+ }
+
+ protected fun replaceText(text: CharSequence?) {
+ clearComposingText()
+ setText(text)
+ setTextSelection(false)
+ }
+
+ fun setCommitCompletionListener(listener: CommitCompletionListener?) {
+ mCommitCompletionListener = listener
+ }
+
+ private val inputMethodManager: InputMethodManager?
+ get() = getContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ fun showInputMethod() {
+ val imm: InputMethodManager? = inputMethodManager
+ if (imm != null) {
+ imm.showSoftInput(this, 0)
+ }
+ }
+
+ fun hideInputMethod() {
+ val imm: InputMethodManager? = inputMethodManager
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0)
+ }
+ }
+
+ @Override
+ override fun onCommitCompletion(completion: CompletionInfo) {
+ if (DBG) Log.d(TAG, "onCommitCompletion($completion)")
+ hideInputMethod()
+ replaceText(completion.getText())
+ if (mCommitCompletionListener != null) {
+ mCommitCompletionListener?.onCommitCompletion(completion.getPosition())
+ }
+ }
+
+ interface CommitCompletionListener {
+ fun onCommitCompletion(position: Int)
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.QueryTextView"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityView.java b/src/com/android/quicksearchbox/ui/SearchActivityView.java
deleted file mode 100644
index 6060e4f..0000000
--- a/src/com/android/quicksearchbox/ui/SearchActivityView.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsListView;
-import android.widget.ImageButton;
-import android.widget.ListAdapter;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-import com.android.quicksearchbox.Logger;
-import com.android.quicksearchbox.QsbApplication;
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.SearchActivity;
-import com.android.quicksearchbox.SourceResult;
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.Suggestions;
-import com.android.quicksearchbox.VoiceSearch;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public abstract class SearchActivityView extends RelativeLayout {
- protected static final boolean DBG = false;
- protected static final String TAG = "QSB.SearchActivityView";
-
- // The string used for privateImeOptions to identify to the IME that it should not show
- // a microphone button since one already exists in the search dialog.
- // TODO: This should move to android-common or something.
- private static final String IME_OPTION_NO_MICROPHONE = "nm";
-
- protected QueryTextView mQueryTextView;
- // True if the query was empty on the previous call to updateQuery()
- protected boolean mQueryWasEmpty = true;
- protected Drawable mQueryTextEmptyBg;
- protected Drawable mQueryTextNotEmptyBg;
-
- protected SuggestionsListView<ListAdapter> mSuggestionsView;
- protected SuggestionsAdapter<ListAdapter> mSuggestionsAdapter;
-
- protected ImageButton mSearchGoButton;
- protected ImageButton mVoiceSearchButton;
-
- protected ButtonsKeyListener mButtonsKeyListener;
-
- private boolean mUpdateSuggestions;
-
- private QueryListener mQueryListener;
- private SearchClickListener mSearchClickListener;
- protected View.OnClickListener mExitClickListener;
-
- public SearchActivityView(Context context) {
- super(context);
- }
-
- public SearchActivityView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchActivityView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onFinishInflate() {
- mQueryTextView = (QueryTextView) findViewById(R.id.search_src_text);
-
- mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions);
- mSuggestionsView.setOnScrollListener(new InputMethodCloser());
- mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener());
- mSuggestionsView.setOnFocusChangeListener(new SuggestListFocusListener());
-
- mSuggestionsAdapter = createSuggestionsAdapter();
- // TODO: why do we need focus listeners both on the SuggestionsView and the individual
- // suggestions?
- mSuggestionsAdapter.setOnFocusChangeListener(new SuggestListFocusListener());
-
- mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn);
- mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
- mVoiceSearchButton.setImageDrawable(getVoiceSearchIcon());
-
- mQueryTextView.addTextChangedListener(new SearchTextWatcher());
- mQueryTextView.setOnEditorActionListener(new QueryTextEditorActionListener());
- mQueryTextView.setOnFocusChangeListener(new QueryTextViewFocusListener());
- mQueryTextEmptyBg = mQueryTextView.getBackground();
-
- mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener());
-
- mButtonsKeyListener = new ButtonsKeyListener();
- mSearchGoButton.setOnKeyListener(mButtonsKeyListener);
- mVoiceSearchButton.setOnKeyListener(mButtonsKeyListener);
-
- mUpdateSuggestions = true;
- }
-
- public abstract void onResume();
-
- public abstract void onStop();
-
- public void onPause() {
- // Override if necessary
- }
-
- public void start() {
- mSuggestionsAdapter.getListAdapter().registerDataSetObserver(new SuggestionsObserver());
- mSuggestionsView.setSuggestionsAdapter(mSuggestionsAdapter);
- }
-
- public void destroy() {
- mSuggestionsView.setSuggestionsAdapter(null); // closes mSuggestionsAdapter
- }
-
- // TODO: Get rid of this. To make it more easily testable,
- // the SearchActivityView should not depend on QsbApplication.
- protected QsbApplication getQsbApplication() {
- return QsbApplication.get(getContext());
- }
-
- protected Drawable getVoiceSearchIcon() {
- return getResources().getDrawable(R.drawable.ic_btn_speak_now);
- }
-
- protected VoiceSearch getVoiceSearch() {
- return getQsbApplication().getVoiceSearch();
- }
-
- protected SuggestionsAdapter<ListAdapter> createSuggestionsAdapter() {
- return new DelayingSuggestionsAdapter<ListAdapter>(new SuggestionsListAdapter(
- getQsbApplication().getSuggestionViewFactory()));
- }
-
- public void setMaxPromotedResults(int maxPromoted) {
- }
-
- public void limitResultsToViewHeight() {
- }
-
- public void setQueryListener(QueryListener listener) {
- mQueryListener = listener;
- }
-
- public void setSearchClickListener(SearchClickListener listener) {
- mSearchClickListener = listener;
- }
-
- public void setVoiceSearchButtonClickListener(View.OnClickListener listener) {
- if (mVoiceSearchButton != null) {
- mVoiceSearchButton.setOnClickListener(listener);
- }
- }
-
- public void setSuggestionClickListener(final SuggestionClickListener listener) {
- mSuggestionsAdapter.setSuggestionClickListener(listener);
- mQueryTextView.setCommitCompletionListener(new QueryTextView.CommitCompletionListener() {
- @Override
- public void onCommitCompletion(int position) {
- mSuggestionsAdapter.onSuggestionClicked(position);
- }
- });
- }
-
- public void setExitClickListener(final View.OnClickListener listener) {
- mExitClickListener = listener;
- }
-
- public Suggestions getSuggestions() {
- return mSuggestionsAdapter.getSuggestions();
- }
-
- public SuggestionCursor getCurrentSuggestions() {
- return mSuggestionsAdapter.getSuggestions().getResult();
- }
-
- public void setSuggestions(Suggestions suggestions) {
- suggestions.acquire();
- mSuggestionsAdapter.setSuggestions(suggestions);
- }
-
- public void clearSuggestions() {
- mSuggestionsAdapter.setSuggestions(null);
- }
-
- public String getQuery() {
- CharSequence q = mQueryTextView.getText();
- return q == null ? "" : q.toString();
- }
-
- public boolean isQueryEmpty() {
- return TextUtils.isEmpty(getQuery());
- }
-
- /**
- * Sets the text in the query box. Does not update the suggestions.
- */
- public void setQuery(String query, boolean selectAll) {
- mUpdateSuggestions = false;
- mQueryTextView.setText(query);
- mQueryTextView.setTextSelection(selectAll);
- mUpdateSuggestions = true;
- }
-
- protected SearchActivity getActivity() {
- Context context = getContext();
- if (context instanceof SearchActivity) {
- return (SearchActivity) context;
- } else {
- return null;
- }
- }
-
- public void hideSuggestions() {
- mSuggestionsView.setVisibility(GONE);
- }
-
- public void showSuggestions() {
- mSuggestionsView.setVisibility(VISIBLE);
- }
-
- public void focusQueryTextView() {
- mQueryTextView.requestFocus();
- }
-
- protected void updateUi() {
- updateUi(isQueryEmpty());
- }
-
- protected void updateUi(boolean queryEmpty) {
- updateQueryTextView(queryEmpty);
- updateSearchGoButton(queryEmpty);
- updateVoiceSearchButton(queryEmpty);
- }
-
- protected void updateQueryTextView(boolean queryEmpty) {
- if (queryEmpty) {
- mQueryTextView.setBackgroundDrawable(mQueryTextEmptyBg);
- mQueryTextView.setHint(null);
- } else {
- mQueryTextView.setBackgroundResource(R.drawable.textfield_search);
- }
- }
-
- private void updateSearchGoButton(boolean queryEmpty) {
- if (queryEmpty) {
- mSearchGoButton.setVisibility(View.GONE);
- } else {
- mSearchGoButton.setVisibility(View.VISIBLE);
- }
- }
-
- protected void updateVoiceSearchButton(boolean queryEmpty) {
- if (shouldShowVoiceSearch(queryEmpty)
- && getVoiceSearch().shouldShowVoiceSearch()) {
- mVoiceSearchButton.setVisibility(View.VISIBLE);
- mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
- } else {
- mVoiceSearchButton.setVisibility(View.GONE);
- mQueryTextView.setPrivateImeOptions(null);
- }
- }
-
- protected boolean shouldShowVoiceSearch(boolean queryEmpty) {
- return queryEmpty;
- }
-
- /**
- * Hides the input method.
- */
- protected void hideInputMethod() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) {
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
-
- public abstract void considerHidingInputMethod();
-
- public void showInputMethodForQuery() {
- mQueryTextView.showInputMethod();
- }
-
- /**
- * Dismiss the activity if BACK is pressed when the search box is empty.
- */
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- SearchActivity activity = getActivity();
- if (activity != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK
- && isQueryEmpty()) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- state.startTracking(event, this);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP
- && !event.isCanceled() && state.isTracking(event)) {
- hideInputMethod();
- activity.onBackPressed();
- return true;
- }
- }
- }
- return super.dispatchKeyEventPreIme(event);
- }
-
- /**
- * If the input method is in fullscreen mode, and the selector corpus
- * is All or Web, use the web search suggestions as completions.
- */
- protected void updateInputMethodSuggestions() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm == null || !imm.isFullscreenMode()) return;
- Suggestions suggestions = mSuggestionsAdapter.getSuggestions();
- if (suggestions == null) return;
- CompletionInfo[] completions = webSuggestionsToCompletions(suggestions);
- if (DBG) Log.d(TAG, "displayCompletions(" + Arrays.toString(completions) + ")");
- imm.displayCompletions(mQueryTextView, completions);
- }
-
- private CompletionInfo[] webSuggestionsToCompletions(Suggestions suggestions) {
- SourceResult cursor = suggestions.getWebResult();
- if (cursor == null) return null;
- int count = cursor.getCount();
- ArrayList<CompletionInfo> completions = new ArrayList<CompletionInfo>(count);
- for (int i = 0; i < count; i++) {
- cursor.moveTo(i);
- String text1 = cursor.getSuggestionText1();
- completions.add(new CompletionInfo(i, i, text1));
- }
- return completions.toArray(new CompletionInfo[completions.size()]);
- }
-
- protected void onSuggestionsChanged() {
- updateInputMethodSuggestions();
- }
-
- protected boolean onSuggestionKeyDown(SuggestionsAdapter<?> adapter,
- long suggestionId, int keyCode, KeyEvent event) {
- // Treat enter or search as a click
- if ( keyCode == KeyEvent.KEYCODE_ENTER
- || keyCode == KeyEvent.KEYCODE_SEARCH
- || keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
- if (adapter != null) {
- adapter.onSuggestionClicked(suggestionId);
- return true;
- } else {
- return false;
- }
- }
-
- return false;
- }
-
- protected boolean onSearchClicked(int method) {
- if (mSearchClickListener != null) {
- return mSearchClickListener.onSearchClicked(method);
- }
- return false;
- }
-
- /**
- * Filters the suggestions list when the search text changes.
- */
- private class SearchTextWatcher implements TextWatcher {
- @Override
- public void afterTextChanged(Editable s) {
- boolean empty = s.length() == 0;
- if (empty != mQueryWasEmpty) {
- mQueryWasEmpty = empty;
- updateUi(empty);
- }
- if (mUpdateSuggestions) {
- if (mQueryListener != null) {
- mQueryListener.onQueryChanged();
- }
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
- }
-
- /**
- * Handles key events on the suggestions list view.
- */
- protected class SuggestionsViewKeyListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && v instanceof SuggestionsListView<?>) {
- SuggestionsListView<?> listView = (SuggestionsListView<?>) v;
- if (onSuggestionKeyDown(listView.getSuggestionsAdapter(),
- listView.getSelectedItemId(), keyCode, event)) {
- return true;
- }
- }
- return forwardKeyToQueryTextView(keyCode, event);
- }
- }
-
- private class InputMethodCloser implements SuggestionsView.OnScrollListener {
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- considerHidingInputMethod();
- }
- }
-
- /**
- * Listens for clicks on the source selector.
- */
- private class SearchGoButtonClickListener implements View.OnClickListener {
- @Override
- public void onClick(View view) {
- onSearchClicked(Logger.SEARCH_METHOD_BUTTON);
- }
- }
-
- /**
- * This class handles enter key presses in the query text view.
- */
- private class QueryTextEditorActionListener implements OnEditorActionListener {
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- boolean consumed = false;
- if (event != null) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- consumed = onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD);
- } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // we have to consume the down event so that we receive the up event too
- consumed = true;
- }
- }
- if (DBG) Log.d(TAG, "onEditorAction consumed=" + consumed);
- return consumed;
- }
- }
-
- /**
- * Handles key events on the search and voice search buttons,
- * by refocusing to EditText.
- */
- private class ButtonsKeyListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- return forwardKeyToQueryTextView(keyCode, event);
- }
- }
-
- private boolean forwardKeyToQueryTextView(int keyCode, KeyEvent event) {
- if (!event.isSystem() && shouldForwardToQueryTextView(keyCode)) {
- if (DBG) Log.d(TAG, "Forwarding key to query box: " + event);
- if (mQueryTextView.requestFocus()) {
- return mQueryTextView.dispatchKeyEvent(event);
- }
- }
- return false;
- }
-
- private boolean shouldForwardToQueryTextView(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_SEARCH:
- return false;
- default:
- return true;
- }
- }
-
- /**
- * Hides the input method when the suggestions get focus.
- */
- private class SuggestListFocusListener implements OnFocusChangeListener {
- @Override
- public void onFocusChange(View v, boolean focused) {
- if (DBG) Log.d(TAG, "Suggestions focus change, now: " + focused);
- if (focused) {
- considerHidingInputMethod();
- }
- }
- }
-
- private class QueryTextViewFocusListener implements OnFocusChangeListener {
- @Override
- public void onFocusChange(View v, boolean focused) {
- if (DBG) Log.d(TAG, "Query focus change, now: " + focused);
- if (focused) {
- // The query box got focus, show the input method
- showInputMethodForQuery();
- }
- }
- }
-
- protected class SuggestionsObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- onSuggestionsChanged();
- }
- }
-
- public interface QueryListener {
- void onQueryChanged();
- }
-
- public interface SearchClickListener {
- boolean onSearchClicked(int method);
- }
-
- private class CloseClickListener implements OnClickListener {
- @Override
- public void onClick(View v) {
- if (!isQueryEmpty()) {
- mQueryTextView.setText("");
- } else {
- mExitClickListener.onClick(v);
- }
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityView.kt b/src/com/android/quicksearchbox/ui/SearchActivityView.kt
new file mode 100644
index 0000000..8e8dcac
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SearchActivityView.kt
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.database.DataSetObserver
+import android.graphics.drawable.Drawable
+import android.text.Editable
+import android.text.TextUtils
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.util.Log
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.CompletionInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.AbsListView
+import android.widget.ImageButton
+import android.widget.ListAdapter
+import android.widget.RelativeLayout
+import android.widget.TextView
+import android.widget.TextView.OnEditorActionListener
+import com.android.quicksearchbox.*
+import com.android.quicksearchbox.R
+import java.util.Arrays
+import kotlin.collections.ArrayList
+
+abstract class SearchActivityView : RelativeLayout {
+ @JvmField protected var mQueryTextView: QueryTextView? = null
+
+ // True if the query was empty on the previous call to updateQuery()
+ @JvmField protected var mQueryWasEmpty = true
+ @JvmField protected var mQueryTextEmptyBg: Drawable? = null
+ protected var mQueryTextNotEmptyBg: Drawable? = null
+ @JvmField protected var mSuggestionsView: SuggestionsListView<ListAdapter?>? = null
+ @JvmField protected var mSuggestionsAdapter: SuggestionsAdapter<ListAdapter?>? = null
+ @JvmField protected var mSearchGoButton: ImageButton? = null
+ @JvmField protected var mVoiceSearchButton: ImageButton? = null
+ @JvmField protected var mButtonsKeyListener: ButtonsKeyListener? = null
+ private var mUpdateSuggestions = false
+ private var mQueryListener: QueryListener? = null
+ private var mSearchClickListener: SearchClickListener? = null
+ @JvmField protected var mExitClickListener: View.OnClickListener? = null
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ @Override
+ protected override fun onFinishInflate() {
+ mQueryTextView = findViewById(R.id.search_src_text) as QueryTextView?
+ mSuggestionsView = findViewById(R.id.suggestions) as SuggestionsView?
+ mSuggestionsView!!.setOnScrollListener(InputMethodCloser() as AbsListView.OnScrollListener?)
+ mSuggestionsView!!.setOnKeyListener(SuggestionsViewKeyListener())
+ mSuggestionsView!!.setOnFocusChangeListener(SuggestListFocusListener())
+ mSuggestionsAdapter = createSuggestionsAdapter()
+ // TODO: why do we need focus listeners both on the SuggestionsView and the individual
+ // suggestions?
+ mSuggestionsAdapter!!.setOnFocusChangeListener(SuggestListFocusListener())
+ mSearchGoButton = findViewById(R.id.search_go_btn) as ImageButton?
+ mVoiceSearchButton = findViewById(R.id.search_voice_btn) as ImageButton?
+ mVoiceSearchButton?.setImageDrawable(voiceSearchIcon)
+ mQueryTextView?.addTextChangedListener(SearchTextWatcher())
+ mQueryTextView?.setOnEditorActionListener(QueryTextEditorActionListener())
+ mQueryTextView?.setOnFocusChangeListener(QueryTextViewFocusListener())
+ mQueryTextEmptyBg = mQueryTextView?.getBackground()
+ mSearchGoButton?.setOnClickListener(SearchGoButtonClickListener())
+ mButtonsKeyListener = ButtonsKeyListener()
+ mSearchGoButton?.setOnKeyListener(mButtonsKeyListener)
+ mVoiceSearchButton?.setOnKeyListener(mButtonsKeyListener)
+ mUpdateSuggestions = true
+ }
+
+ abstract fun onResume()
+ abstract fun onStop()
+ fun onPause() {
+ // Override if necessary
+ }
+
+ fun start() {
+ mSuggestionsAdapter?.listAdapter?.registerDataSetObserver(SuggestionsObserver())
+ mSuggestionsView!!.setSuggestionsAdapter(mSuggestionsAdapter)
+ }
+
+ fun destroy() {
+ mSuggestionsView!!.setSuggestionsAdapter(null) // closes mSuggestionsAdapter
+ }
+
+ // TODO: Get rid of this. To make it more easily testable,
+ // the SearchActivityView should not depend on QsbApplication.
+ protected val qsbApplication: QsbApplication
+ get() = QsbApplication[getContext()]
+ protected val voiceSearchIcon: Drawable
+ get() = getResources().getDrawable(R.drawable.ic_btn_speak_now, null)
+ protected val voiceSearch: VoiceSearch?
+ get() = qsbApplication.voiceSearch
+
+ protected fun createSuggestionsAdapter(): SuggestionsAdapter<ListAdapter?> {
+ return DelayingSuggestionsAdapter(SuggestionsListAdapter(qsbApplication.suggestionViewFactory))
+ }
+
+ @Suppress("UNUSED_PARAMETER") fun setMaxPromotedResults(maxPromoted: Int) {}
+
+ fun limitResultsToViewHeight() {}
+
+ fun setQueryListener(listener: QueryListener?) {
+ mQueryListener = listener
+ }
+
+ fun setSearchClickListener(listener: SearchClickListener?) {
+ mSearchClickListener = listener
+ }
+
+ fun setVoiceSearchButtonClickListener(listener: View.OnClickListener?) {
+ if (mVoiceSearchButton != null) {
+ mVoiceSearchButton?.setOnClickListener(listener)
+ }
+ }
+
+ fun setSuggestionClickListener(listener: SuggestionClickListener?) {
+ mSuggestionsAdapter!!.setSuggestionClickListener(listener)
+ mQueryTextView!!.setCommitCompletionListener(
+ object : QueryTextView.CommitCompletionListener {
+ @Override
+ override fun onCommitCompletion(position: Int) {
+ mSuggestionsAdapter!!.onSuggestionClicked(position.toLong())
+ }
+ }
+ )
+ }
+
+ fun setExitClickListener(listener: View.OnClickListener?) {
+ mExitClickListener = listener
+ }
+
+ var suggestions: Suggestions?
+ get() = mSuggestionsAdapter?.suggestions
+ set(suggestions) {
+ suggestions?.acquire()
+ mSuggestionsAdapter?.suggestions = suggestions
+ }
+ val currentSuggestions: SuggestionCursor
+ get() = mSuggestionsAdapter?.suggestions?.getResult() as SuggestionCursor
+
+ fun clearSuggestions() {
+ mSuggestionsAdapter?.suggestions = null
+ }
+
+ val query: String
+ get() {
+ val q: CharSequence? = mQueryTextView?.getText()
+ return q.toString()
+ }
+ val isQueryEmpty: Boolean
+ get() = TextUtils.isEmpty(query)
+
+ /** Sets the text in the query box. Does not update the suggestions. */
+ fun setQuery(query: String?, selectAll: Boolean) {
+ mUpdateSuggestions = false
+ mQueryTextView?.setText(query)
+ mQueryTextView!!.setTextSelection(selectAll)
+ mUpdateSuggestions = true
+ }
+
+ protected val activity: SearchActivity?
+ get() {
+ val context: Context = getContext()
+ return if (context is SearchActivity) {
+ context
+ } else {
+ null
+ }
+ }
+
+ fun hideSuggestions() {
+ mSuggestionsView!!.setVisibility(GONE)
+ }
+
+ fun showSuggestions() {
+ mSuggestionsView!!.setVisibility(VISIBLE)
+ }
+
+ fun focusQueryTextView() {
+ mQueryTextView?.requestFocus()
+ }
+
+ protected fun updateUi(queryEmpty: Boolean = isQueryEmpty) {
+ updateQueryTextView(queryEmpty)
+ updateSearchGoButton(queryEmpty)
+ updateVoiceSearchButton(queryEmpty)
+ }
+
+ protected fun updateQueryTextView(queryEmpty: Boolean) {
+ if (queryEmpty) {
+ mQueryTextView?.setBackground(mQueryTextEmptyBg)
+ mQueryTextView?.setHint(null)
+ } else {
+ mQueryTextView?.setBackgroundResource(R.drawable.textfield_search)
+ }
+ }
+
+ private fun updateSearchGoButton(queryEmpty: Boolean) {
+ if (queryEmpty) {
+ mSearchGoButton?.setVisibility(View.GONE)
+ } else {
+ mSearchGoButton?.setVisibility(View.VISIBLE)
+ }
+ }
+
+ protected fun updateVoiceSearchButton(queryEmpty: Boolean) {
+ if (shouldShowVoiceSearch(queryEmpty) && voiceSearch!!.shouldShowVoiceSearch()) {
+ mVoiceSearchButton?.setVisibility(View.VISIBLE)
+ mQueryTextView?.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE)
+ } else {
+ mVoiceSearchButton?.setVisibility(View.GONE)
+ mQueryTextView?.setPrivateImeOptions(null)
+ }
+ }
+
+ protected fun shouldShowVoiceSearch(queryEmpty: Boolean): Boolean {
+ return queryEmpty
+ }
+
+ /** Hides the input method. */
+ protected fun hideInputMethod() {
+ val imm: InputMethodManager? =
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0)
+ }
+ }
+
+ abstract fun considerHidingInputMethod()
+ fun showInputMethodForQuery() {
+ mQueryTextView!!.showInputMethod()
+ }
+
+ /** Dismiss the activity if BACK is pressed when the search box is empty. */
+ @Suppress("Deprecation")
+ @Override
+ override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ val activity = activity
+ if (activity != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK && isQueryEmpty) {
+ val state: KeyEvent.DispatcherState? = getKeyDispatcherState()
+ if (state != null) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ state.startTracking(event, this)
+ return true
+ } else if (
+ event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled() && state.isTracking(event)
+ ) {
+ hideInputMethod()
+ activity.onBackPressed()
+ return true
+ }
+ }
+ }
+ return super.dispatchKeyEventPreIme(event)
+ }
+
+ /**
+ * If the input method is in fullscreen mode, and the selector corpus is All or Web, use the web
+ * search suggestions as completions.
+ */
+ protected fun updateInputMethodSuggestions() {
+ val imm: InputMethodManager? =
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ if (imm == null || !imm.isFullscreenMode()) return
+ val suggestions: Suggestions = mSuggestionsAdapter?.suggestions ?: return
+ val completions: Array<CompletionInfo>? = webSuggestionsToCompletions(suggestions)
+ if (DBG) Log.d(TAG, "displayCompletions(" + Arrays.toString(completions).toString() + ")")
+ imm.displayCompletions(mQueryTextView, completions)
+ }
+
+ private fun webSuggestionsToCompletions(suggestions: Suggestions): Array<CompletionInfo>? {
+ val cursor = suggestions.getWebResult() ?: return null
+ val count: Int = cursor.count
+ val completions: ArrayList<CompletionInfo> = ArrayList<CompletionInfo>(count)
+ for (i in 0 until count) {
+ cursor.moveTo(i)
+ val text1: String? = cursor.suggestionText1
+ completions.add(CompletionInfo(i.toLong(), i, text1))
+ }
+ return completions.toArray(arrayOfNulls<CompletionInfo>(completions.size))
+ }
+
+ protected fun onSuggestionsChanged() {
+ updateInputMethodSuggestions()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ protected fun onSuggestionKeyDown(
+ adapter: SuggestionsAdapter<*>?,
+ suggestionId: Long,
+ keyCode: Int,
+ event: KeyEvent?
+ ): Boolean {
+ // Treat enter or search as a click
+ return if (
+ keyCode == KeyEvent.KEYCODE_ENTER ||
+ keyCode == KeyEvent.KEYCODE_SEARCH ||
+ keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ ) {
+ if (adapter != null) {
+ adapter.onSuggestionClicked(suggestionId)
+ true
+ } else {
+ false
+ }
+ } else false
+ }
+
+ protected fun onSearchClicked(method: Int): Boolean {
+ return if (mSearchClickListener != null) {
+ mSearchClickListener!!.onSearchClicked(method)
+ } else false
+ }
+
+ /** Filters the suggestions list when the search text changes. */
+ private inner class SearchTextWatcher : TextWatcher {
+ @Override
+ override fun afterTextChanged(s: Editable) {
+ val empty = s.length == 0
+ if (empty != mQueryWasEmpty) {
+ mQueryWasEmpty = empty
+ updateUi(empty)
+ }
+ if (mUpdateSuggestions) {
+ if (mQueryListener != null) {
+ mQueryListener!!.onQueryChanged()
+ }
+ }
+ }
+
+ @Override
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ @Override override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ }
+
+ /** Handles key events on the suggestions list view. */
+ protected inner class SuggestionsViewKeyListener : View.OnKeyListener {
+ @Override
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && v is SuggestionsListView<*>) {
+ val listView = v as SuggestionsListView<*>
+ if (
+ onSuggestionKeyDown(
+ listView.getSuggestionsAdapter(),
+ listView.getSelectedItemId(),
+ keyCode,
+ event
+ )
+ ) {
+ return true
+ }
+ }
+ return forwardKeyToQueryTextView(keyCode, event)
+ }
+ }
+
+ private inner class InputMethodCloser : AbsListView.OnScrollListener {
+ @Override
+ override fun onScroll(
+ view: AbsListView?,
+ firstVisibleItem: Int,
+ visibleItemCount: Int,
+ totalItemCount: Int
+ ) {}
+
+ @Override
+ override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+ considerHidingInputMethod()
+ }
+ }
+
+ /** Listens for clicks on the source selector. */
+ private inner class SearchGoButtonClickListener : View.OnClickListener {
+ @Override
+ override fun onClick(view: View?) {
+ onSearchClicked(Logger.SEARCH_METHOD_BUTTON)
+ }
+ }
+
+ /** This class handles enter key presses in the query text view. */
+ private inner class QueryTextEditorActionListener : OnEditorActionListener {
+ @Override
+ override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
+ var consumed = false
+ if (event != null) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ consumed = onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD)
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ // we have to consume the down event so that we receive the up event too
+ consumed = true
+ }
+ }
+ if (DBG) Log.d(TAG, "onEditorAction consumed=$consumed")
+ return consumed
+ }
+ }
+
+ /** Handles key events on the search and voice search buttons, by refocusing to EditText. */
+ protected inner class ButtonsKeyListener : View.OnKeyListener {
+ @Override
+ override fun onKey(v: View?, keyCode: Int, event: KeyEvent): Boolean {
+ return forwardKeyToQueryTextView(keyCode, event)
+ }
+ }
+
+ private fun forwardKeyToQueryTextView(keyCode: Int, event: KeyEvent): Boolean {
+ if (!event.isSystem() && shouldForwardToQueryTextView(keyCode)) {
+ if (DBG) Log.d(TAG, "Forwarding key to query box: $event")
+ if (mQueryTextView!!.requestFocus()) {
+ return mQueryTextView!!.dispatchKeyEvent(event)
+ }
+ }
+ return false
+ }
+
+ private fun shouldForwardToQueryTextView(keyCode: Int): Boolean {
+ return when (keyCode) {
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_CENTER,
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_SEARCH -> false
+ else -> true
+ }
+ }
+
+ /** Hides the input method when the suggestions get focus. */
+ private inner class SuggestListFocusListener : OnFocusChangeListener {
+ @Override
+ override fun onFocusChange(v: View?, focused: Boolean) {
+ if (DBG) Log.d(TAG, "Suggestions focus change, now: $focused")
+ if (focused) {
+ considerHidingInputMethod()
+ }
+ }
+ }
+
+ private inner class QueryTextViewFocusListener : OnFocusChangeListener {
+ @Override
+ override fun onFocusChange(v: View?, focused: Boolean) {
+ if (DBG) Log.d(TAG, "Query focus change, now: $focused")
+ if (focused) {
+ // The query box got focus, show the input method
+ showInputMethodForQuery()
+ }
+ }
+ }
+
+ protected inner class SuggestionsObserver : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ onSuggestionsChanged()
+ }
+ }
+
+ interface QueryListener {
+ fun onQueryChanged()
+ }
+
+ interface SearchClickListener {
+ fun onSearchClicked(method: Int): Boolean
+ }
+
+ private inner class CloseClickListener : OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ if (!isQueryEmpty) {
+ mQueryTextView?.setText("")
+ } else {
+ mExitClickListener?.onClick(v)
+ }
+ }
+ }
+
+ companion object {
+ protected const val DBG = false
+ protected const val TAG = "QSB.SearchActivityView"
+
+ // The string used for privateImeOptions to identify to the IME that it should not show
+ // a microphone button since one already exists in the search dialog.
+ // TODO: This should move to android-common or something.
+ private const val IME_OPTION_NO_MICROPHONE = "nm"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.java b/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.java
deleted file mode 100644
index 9288fb6..0000000
--- a/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Source;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageButton;
-
-/**
- * Finishes the containing activity on BACK, even if input method is showing.
- */
-public class SearchActivityViewSinglePane extends SearchActivityView {
-
- public SearchActivityViewSinglePane(Context context) {
- super(context);
- }
-
- public SearchActivityViewSinglePane(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchActivityViewSinglePane(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void onResume() {
- focusQueryTextView();
- }
-
- @Override
- public void considerHidingInputMethod() {
- mQueryTextView.hideInputMethod();
- }
-
- @Override
- public void onStop() {
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt b/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt
new file mode 100644
index 0000000..b4485e7
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+
+/** Finishes the containing activity on BACK, even if input method is showing. */
+class SearchActivityViewSinglePane : SearchActivityView {
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ @Override
+ override fun onResume() {
+ focusQueryTextView()
+ }
+
+ @Override
+ override fun considerHidingInputMethod() {
+ mQueryTextView!!.hideInputMethod()
+ }
+
+ @Override override fun onStop() {}
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java b/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
deleted file mode 100644
index da062cd..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-/**
- * Listener interface for clicks on suggestions.
- */
-public interface SuggestionClickListener {
-
- /**
- * Called when a suggestion is clicked.
- *
- * @param adapter Adapter that contains the clicked suggestion.
- * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this
- * will be the position within the list.
- */
- void onSuggestionClicked(SuggestionsAdapter<?> adapter, long suggestionId);
-
- /**
- * Called when the "query refine" button of a suggestion is clicked.
- *
- * @param adapter Adapter that contains the clicked suggestion.
- * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this
- * will be the position within the list.
- */
- void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long suggestionId);
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionClickListener.kt b/src/com/android/quicksearchbox/ui/SuggestionClickListener.kt
new file mode 100644
index 0000000..ae00ec1
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionClickListener.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+/** Listener interface for clicks on suggestions. */
+interface SuggestionClickListener {
+ /**
+ * Called when a suggestion is clicked.
+ *
+ * @param adapter Adapter that contains the clicked suggestion.
+ * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this will
+ * be the position within the list.
+ */
+ fun onSuggestionClicked(adapter: SuggestionsAdapter<*>?, suggestionId: Long)
+
+ /**
+ * Called when the "query refine" button of a suggestion is clicked.
+ *
+ * @param adapter Adapter that contains the clicked suggestion.
+ * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this will
+ * be the position within the list.
+ */
+ fun onSuggestionQueryRefineClicked(adapter: SuggestionsAdapter<*>?, suggestionId: Long)
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionView.java b/src/com/android/quicksearchbox/ui/SuggestionView.java
deleted file mode 100644
index 636ecd5..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionView.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.Suggestion;
-
-/**
- * Interface to be implemented by any view appearing in the list of suggestions.
- */
-public interface SuggestionView {
- /**
- * Set the view's contents based on the given suggestion.
- */
- void bindAsSuggestion(Suggestion suggestion, String userQuery);
-
- /**
- * Binds this view to a list adapter.
- *
- * @param adapter The adapter of the list which the view is appearing in
- * @param position The position of this view with the list.
- */
- void bindAdapter(SuggestionsAdapter<?> adapter, long position);
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionView.kt b/src/com/android/quicksearchbox/ui/SuggestionView.kt
new file mode 100644
index 0000000..7c4364e
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionView.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import com.android.quicksearchbox.Suggestion
+
+/** Interface to be implemented by any view appearing in the list of suggestions. */
+interface SuggestionView {
+ /** Set the view's contents based on the given suggestion. */
+ fun bindAsSuggestion(suggestion: Suggestion?, userQuery: String?)
+
+ /**
+ * Binds this view to a list adapter.
+ *
+ * @param adapter The adapter of the list which the view is appearing in
+ * @param position The position of this view with the list.
+ */
+ fun bindAdapter(adapter: SuggestionsAdapter<*>?, position: Long)
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java b/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java
deleted file mode 100644
index 27cb596..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.SuggestionCursor;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.Collection;
-
-/**
- * Factory interface for suggestion views.
- */
-public interface SuggestionViewFactory {
-
- /**
- * Returns all the view types that are used by this factory. Each view type corresponds to a
- * specific layout that is used to display suggestions. The returned set must have at least one
- * item in it.
- *
- * View types must be unique across all suggestion view factories.
- */
- Collection<String> getSuggestionViewTypes();
-
- /**
- * Returns the view type to be used for displaying the given suggestion. This MUST correspond to
- * one of the view types returned by {@link #getSuggestionViewTypes()}.
- */
- String getViewType(Suggestion suggestion);
-
- /**
- * Gets a view corresponding to the current suggestion in the given cursor.
- *
- * @param convertView The old view to reuse, if possible. Note: You should check that this view
- * is non-null and of an appropriate type before using. If it is not possible to convert
- * this view to display the correct data, this method can create a new view.
- * @param parent The parent that this view will eventually be attached to
- * @return A View corresponding to the data within this suggestion.
- */
- View getView(SuggestionCursor suggestion, String userQuery, View convertView, ViewGroup parent);
-
- /**
- * Checks whether this factory can create views for the given suggestion.
- */
- boolean canCreateView(Suggestion suggestion);
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewFactory.kt b/src/com/android/quicksearchbox/ui/SuggestionViewFactory.kt
new file mode 100644
index 0000000..a33886c
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionViewFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+
+/** Factory interface for suggestion views. */
+interface SuggestionViewFactory {
+ /**
+ * Returns all the view types that are used by this factory. Each view type corresponds to a
+ * specific layout that is used to display suggestions. The returned set must have at least one
+ * item in it.
+ *
+ * View types must be unique across all suggestion view factories.
+ */
+ val suggestionViewTypes: Collection<String>
+
+ /**
+ * Returns the view type to be used for displaying the given suggestion. This MUST correspond to
+ * one of the view types returned by [.getSuggestionViewTypes].
+ */
+ fun getViewType(suggestion: Suggestion?): String?
+
+ /**
+ * Gets a view corresponding to the current suggestion in the given cursor.
+ *
+ * @param convertView The old view to reuse, if possible. Note: You should check that this view is
+ * non-null and of an appropriate type before using. If it is not possible to convert this view to
+ * display the correct data, this method can create a new view.
+ * @param parent The parent that this view will eventually be attached to
+ * @return A View corresponding to the data within this suggestion.
+ */
+ fun getView(
+ suggestion: SuggestionCursor?,
+ userQuery: String?,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View?
+
+ /** Checks whether this factory can create views for the given suggestion. */
+ fun canCreateView(suggestion: Suggestion?): Boolean
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java
deleted file mode 100644
index 9275b6e..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.SuggestionCursor;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Suggestion view factory that inflates views from XML.
- */
-public class SuggestionViewInflater implements SuggestionViewFactory {
-
- private final String mViewType;
- private final Class<?> mViewClass;
- private final int mLayoutId;
- private final Context mContext;
-
- /**
- * @param viewType The unique type of views inflated by this factory
- * @param viewClass The expected type of view classes.
- * @param layoutId resource ID of layout to use.
- * @param context Context to use for inflating the views.
- */
- public SuggestionViewInflater(String viewType, Class<? extends SuggestionView> viewClass,
- int layoutId, Context context) {
- mViewType = viewType;
- mViewClass = viewClass;
- mLayoutId = layoutId;
- mContext = context;
- }
-
- protected LayoutInflater getInflater() {
- return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- public Collection<String> getSuggestionViewTypes() {
- return Collections.singletonList(mViewType);
- }
-
- public View getView(SuggestionCursor suggestion, String userQuery,
- View convertView, ViewGroup parent) {
- if (convertView == null || !convertView.getClass().equals(mViewClass)) {
- int layoutId = mLayoutId;
- convertView = getInflater().inflate(layoutId, parent, false);
- }
- if (!(convertView instanceof SuggestionView)) {
- throw new IllegalArgumentException("Not a SuggestionView: " + convertView);
- }
- ((SuggestionView) convertView).bindAsSuggestion(suggestion, userQuery);
- return convertView;
- }
-
- public String getViewType(Suggestion suggestion) {
- return mViewType;
- }
-
- public boolean canCreateView(Suggestion suggestion) {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt
new file mode 100644
index 0000000..acfa592
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+
+/** Suggestion view factory that inflates views from XML. */
+open class SuggestionViewInflater(
+ private val mViewType: String,
+ viewClass: Class<out SuggestionView?>,
+ layoutId: Int,
+ context: Context?
+) : SuggestionViewFactory {
+ private val mViewClass: Class<*>
+ private val mLayoutId: Int
+ private val mContext: Context?
+
+ protected val inflater: LayoutInflater
+ get() = mContext?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+
+ override val suggestionViewTypes: Collection<String>
+ get() = listOf(mViewType)
+
+ override fun getView(
+ suggestion: SuggestionCursor?,
+ userQuery: String?,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View? {
+ var mConvertView: View? = convertView
+ if (mConvertView == null || !mConvertView::class.equals(mViewClass)) {
+ val layoutId = mLayoutId
+ mConvertView = inflater.inflate(layoutId, parent, false)
+ }
+ if (mConvertView !is SuggestionView) {
+ throw IllegalArgumentException("Not a SuggestionView: $mConvertView")
+ }
+ (mConvertView as SuggestionView).bindAsSuggestion(suggestion, userQuery)
+ return mConvertView
+ }
+
+ override fun getViewType(suggestion: Suggestion?): String {
+ return mViewType
+ }
+
+ override fun canCreateView(suggestion: Suggestion?): Boolean {
+ return true
+ }
+
+ /**
+ * @param viewType The unique type of views inflated by this factory
+ * @param viewClass The expected type of view classes.
+ * @param layoutId resource ID of layout to use.
+ * @param context Context to use for inflating the views.
+ */
+ init {
+ mViewClass = viewClass
+ mLayoutId = layoutId
+ mContext = context
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java b/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java
deleted file mode 100644
index 825ae0d..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
-import com.android.quicksearchbox.Suggestions;
-
-import android.view.View.OnFocusChangeListener;
-import android.widget.ExpandableListAdapter;
-import android.widget.ListAdapter;
-
-/**
- * Interface for suggestions adapters.
- *
- * @param <A> the adapter class used by the UI, probably either {@link ListAdapter} or
- * {@link ExpandableListAdapter}.
- */
-public interface SuggestionsAdapter<A> {
-
- /**
- * Sets the listener to be notified of clicks on suggestions.
- */
- void setSuggestionClickListener(SuggestionClickListener listener);
-
- /**
- * Sets the listener to be notified of focus change events on suggestion views.
- */
- void setOnFocusChangeListener(OnFocusChangeListener l);
-
- /**
- * Sets the current suggestions.
- */
- void setSuggestions(Suggestions suggestions);
-
- /**
- * Indicates if there's any suggestions in this adapter.
- */
- boolean isEmpty();
-
- /**
- * Gets the current suggestions.
- */
- Suggestions getSuggestions();
-
- /**
- * Gets the cursor and position corresponding to the given suggestion ID.
- * @param suggestionId Suggestion ID.
- */
- SuggestionPosition getSuggestion(long suggestionId);
-
- /**
- * Handles a regular click on a suggestion.
- *
- * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this
- * will be the position within the list.
- */
- void onSuggestionClicked(long suggestionId);
-
- /**
- * Handles a click on the query refinement button.
- *
- * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this
- * will be the position within the list.
- */
- void onSuggestionQueryRefineClicked(long suggestionId);
-
- /**
- * Gets the adapter to be used by the UI view.
- */
- A getListAdapter();
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapter.kt b/src/com/android/quicksearchbox/ui/SuggestionsAdapter.kt
new file mode 100644
index 0000000..687fd42
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsAdapter.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.view.View.OnFocusChangeListener
+import android.widget.ExpandableListAdapter
+import android.widget.ListAdapter
+import com.android.quicksearchbox.SuggestionPosition
+import com.android.quicksearchbox.Suggestions
+
+/**
+ * Interface for suggestions adapters.
+ *
+ * @param <A> the adapter class used by the UI, probably either [ListAdapter] or
+ * [ExpandableListAdapter].
+ */
+interface SuggestionsAdapter<A> {
+ /** Sets the listener to be notified of clicks on suggestions. */
+ fun setSuggestionClickListener(listener: SuggestionClickListener?)
+
+ /** Sets the listener to be notified of focus change events on suggestion views. */
+ fun setOnFocusChangeListener(l: OnFocusChangeListener?)
+
+ /** Indicates if there's any suggestions in this adapter. */
+ val isEmpty: Boolean
+ /** Gets the current suggestions. */
+ /** Sets the current suggestions. */
+ var suggestions: Suggestions?
+
+ /**
+ * Gets the cursor and position corresponding to the given suggestion ID.
+ * @param suggestionId Suggestion ID.
+ */
+ fun getSuggestion(suggestionId: Long): SuggestionPosition?
+
+ /**
+ * Handles a regular click on a suggestion.
+ *
+ * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this will
+ * be the position within the list.
+ */
+ fun onSuggestionClicked(suggestionId: Long)
+
+ /**
+ * Handles a click on the query refinement button.
+ *
+ * @param suggestionId The ID of the suggestion clicked. If the suggestion list is flat, this will
+ * be the position within the list.
+ */
+ fun onSuggestionQueryRefineClicked(suggestionId: Long)
+
+ /** Gets the adapter to be used by the UI view. */
+ val listAdapter: A
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.java b/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.java
deleted file mode 100644
index 244e3f9..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
-import com.android.quicksearchbox.Suggestions;
-
-import android.database.DataSetObserver;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnFocusChangeListener;
-import android.view.ViewGroup;
-
-import java.util.HashMap;
-
-/**
- * Base class for suggestions adapters. The templated class A is the list adapter class.
- */
-public abstract class SuggestionsAdapterBase<A> implements SuggestionsAdapter<A> {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SuggestionsAdapter";
-
- private DataSetObserver mDataSetObserver;
-
- private SuggestionCursor mCurrentSuggestions;
- private final HashMap<String, Integer> mViewTypeMap;
- private final SuggestionViewFactory mViewFactory;
-
- private Suggestions mSuggestions;
-
- private SuggestionClickListener mSuggestionClickListener;
- private OnFocusChangeListener mOnFocusChangeListener;
-
- private boolean mClosed = false;
-
- protected SuggestionsAdapterBase(SuggestionViewFactory viewFactory) {
- mViewFactory = viewFactory;
- mViewTypeMap = new HashMap<String, Integer>();
- for (String viewType : mViewFactory.getSuggestionViewTypes()) {
- if (!mViewTypeMap.containsKey(viewType)) {
- mViewTypeMap.put(viewType, mViewTypeMap.size());
- }
- }
- }
-
- @Override
- public abstract boolean isEmpty();
-
- public boolean isClosed() {
- return mClosed;
- }
-
- public void close() {
- setSuggestions(null);
- mClosed = true;
- }
-
- @Override
- public void setSuggestionClickListener(SuggestionClickListener listener) {
- mSuggestionClickListener = listener;
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener l) {
- mOnFocusChangeListener = l;
- }
-
- @Override
- public void setSuggestions(Suggestions suggestions) {
- if (mSuggestions == suggestions) {
- return;
- }
- if (mClosed) {
- if (suggestions != null) {
- suggestions.release();
- }
- return;
- }
- if (mDataSetObserver == null) {
- mDataSetObserver = new MySuggestionsObserver();
- }
- // TODO: delay the change if there are no suggestions for the currently visible tab.
- if (mSuggestions != null) {
- mSuggestions.unregisterDataSetObserver(mDataSetObserver);
- mSuggestions.release();
- }
- mSuggestions = suggestions;
- if (mSuggestions != null) {
- mSuggestions.registerDataSetObserver(mDataSetObserver);
- }
- onSuggestionsChanged();
- }
-
- @Override
- public Suggestions getSuggestions() {
- return mSuggestions;
- }
-
- @Override
- public abstract SuggestionPosition getSuggestion(long suggestionId);
-
- protected int getCount() {
- return mCurrentSuggestions == null ? 0 : mCurrentSuggestions.getCount();
- }
-
- protected SuggestionPosition getSuggestion(int position) {
- if (mCurrentSuggestions == null) return null;
- return new SuggestionPosition(mCurrentSuggestions, position);
- }
-
- protected int getViewTypeCount() {
- return mViewTypeMap.size();
- }
-
- private String suggestionViewType(Suggestion suggestion) {
- String viewType = mViewFactory.getViewType(suggestion);
- if (!mViewTypeMap.containsKey(viewType)) {
- throw new IllegalStateException("Unknown viewType " + viewType);
- }
- return viewType;
- }
-
- protected int getSuggestionViewType(SuggestionCursor cursor, int position) {
- if (cursor == null) {
- return 0;
- }
- cursor.moveTo(position);
- return mViewTypeMap.get(suggestionViewType(cursor));
- }
-
- protected int getSuggestionViewTypeCount() {
- return mViewTypeMap.size();
- }
-
- protected View getView(SuggestionCursor suggestions, int position, long suggestionId,
- View convertView, ViewGroup parent) {
- suggestions.moveTo(position);
- View v = mViewFactory.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
- if (v instanceof SuggestionView) {
- ((SuggestionView) v).bindAdapter(this, suggestionId);
- } else {
- SuggestionViewClickListener l = new SuggestionViewClickListener(suggestionId);
- v.setOnClickListener(l);
- }
-
- if (mOnFocusChangeListener != null) {
- v.setOnFocusChangeListener(mOnFocusChangeListener);
- }
- return v;
- }
-
- protected void onSuggestionsChanged() {
- if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
- SuggestionCursor cursor = null;
- if (mSuggestions != null) {
- cursor = mSuggestions.getResult();
- }
- changeSuggestions(cursor);
- }
-
- public SuggestionCursor getCurrentSuggestions() {
- return mCurrentSuggestions;
- }
-
- /**
- * Replace the cursor.
- *
- * This does not close the old cursor. Instead, all the cursors are closed in
- * {@link #setSuggestions(Suggestions)}.
- */
- private void changeSuggestions(SuggestionCursor newCursor) {
- if (DBG) {
- Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
- (newCursor == null ? 0 : newCursor.getCount()));
- }
- if (newCursor == mCurrentSuggestions) {
- if (newCursor != null) {
- // Shortcuts may have changed without the cursor changing.
- notifyDataSetChanged();
- }
- return;
- }
- mCurrentSuggestions = newCursor;
- if (mCurrentSuggestions != null) {
- notifyDataSetChanged();
- } else {
- notifyDataSetInvalidated();
- }
- }
-
- @Override
- public void onSuggestionClicked(long suggestionId) {
- if (mClosed) {
- Log.w(TAG, "onSuggestionClicked after close");
- } else if (mSuggestionClickListener != null) {
- mSuggestionClickListener.onSuggestionClicked(this, suggestionId);
- }
- }
-
- @Override
- public void onSuggestionQueryRefineClicked(long suggestionId) {
- if (mClosed) {
- Log.w(TAG, "onSuggestionQueryRefineClicked after close");
- } else if (mSuggestionClickListener != null) {
- mSuggestionClickListener.onSuggestionQueryRefineClicked(this, suggestionId);
- }
- }
-
- @Override
- public abstract A getListAdapter();
-
- protected abstract void notifyDataSetInvalidated();
-
- protected abstract void notifyDataSetChanged();
-
- private class MySuggestionsObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- onSuggestionsChanged();
- }
- }
-
- private class SuggestionViewClickListener implements View.OnClickListener {
- private final long mSuggestionId;
- public SuggestionViewClickListener(long suggestionId) {
- mSuggestionId = suggestionId;
- }
- @Override
- public void onClick(View v) {
- onSuggestionClicked(mSuggestionId);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt b/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt
new file mode 100644
index 0000000..25218a5
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.database.DataSetObserver
+import android.util.Log
+import android.view.View
+import android.view.View.OnFocusChangeListener
+import android.view.ViewGroup
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+import com.android.quicksearchbox.Suggestions
+import kotlin.collections.HashMap
+
+/** Base class for suggestions adapters. The templated class A is the list adapter class. */
+abstract class SuggestionsAdapterBase<A>
+protected constructor(private val mViewFactory: SuggestionViewFactory) : SuggestionsAdapter<A> {
+ private var mDataSetObserver: DataSetObserver? = null
+ var currentSuggestions: SuggestionCursor? = null
+ private set
+ private val mViewTypeMap: HashMap<String, Int>
+ private var mSuggestions: Suggestions? = null
+ private var mSuggestionClickListener: SuggestionClickListener? = null
+ private var mOnFocusChangeListener: OnFocusChangeListener? = null
+ var isClosed = false
+ private set
+
+ @get:Override abstract override val isEmpty: Boolean
+ fun close() {
+ suggestions = null
+ isClosed = true
+ }
+
+ @Override
+ override fun setSuggestionClickListener(listener: SuggestionClickListener?) {
+ mSuggestionClickListener = listener
+ }
+
+ @Override
+ override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
+ mOnFocusChangeListener = l
+ }
+
+ // TODO: delay the change if there are no suggestions for the currently visible tab.
+ @get:Override
+ @set:Override
+ override var suggestions: Suggestions?
+ get() = mSuggestions!!
+ set(suggestions) {
+ if (mSuggestions === suggestions) {
+ return
+ }
+ if (isClosed) {
+ suggestions?.release()
+ return
+ }
+ if (mDataSetObserver == null) {
+ mDataSetObserver = MySuggestionsObserver()
+ }
+ // TODO: delay the change if there are no suggestions for the currently visible tab.
+ if (mSuggestions != null) {
+ mSuggestions!!.unregisterDataSetObserver(mDataSetObserver)
+ mSuggestions!!.release()
+ }
+ mSuggestions = suggestions
+ if (mSuggestions != null) {
+ mSuggestions!!.registerDataSetObserver(mDataSetObserver)
+ }
+ onSuggestionsChanged()
+ }
+
+ @Override abstract override fun getSuggestion(suggestionId: Long): SuggestionPosition
+ protected val count: Int
+ get() = if (currentSuggestions == null) 0 else currentSuggestions!!.count
+
+ protected fun getSuggestion(position: Int): SuggestionPosition? {
+ return if (currentSuggestions == null) null
+ else SuggestionPosition(currentSuggestions!!, position)
+ }
+
+ protected val viewTypeCount: Int
+ get() = mViewTypeMap.size
+
+ private fun suggestionViewType(suggestion: Suggestion): String? {
+ val viewType = mViewFactory.getViewType(suggestion)
+ if (!mViewTypeMap.containsKey(viewType)) {
+ throw IllegalStateException("Unknown viewType $viewType")
+ }
+ return viewType
+ }
+
+ protected fun getSuggestionViewType(cursor: SuggestionCursor?, position: Int): Int {
+ if (cursor == null) {
+ return 0
+ }
+ cursor.moveTo(position)
+ return mViewTypeMap.get(suggestionViewType(cursor)!!) as Int
+ }
+
+ protected val suggestionViewTypeCount: Int
+ get() = mViewTypeMap.size
+
+ protected fun getView(
+ suggestions: SuggestionCursor?,
+ position: Int,
+ suggestionId: Long,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View? {
+ suggestions?.moveTo(position)
+ val v: View? = mViewFactory.getView(suggestions, suggestions?.userQuery, convertView, parent)
+ if (v is SuggestionView) {
+ (v as SuggestionView?)!!.bindAdapter(this, suggestionId)
+ } else {
+ val l = SuggestionViewClickListener(suggestionId)
+ v?.setOnClickListener(l)
+ }
+ if (mOnFocusChangeListener != null) {
+ v?.setOnFocusChangeListener(mOnFocusChangeListener)
+ }
+ return v
+ }
+
+ protected fun onSuggestionsChanged() {
+ if (DBG) Log.d(TAG, "onSuggestionsChanged($mSuggestions)")
+ var cursor: SuggestionCursor? = null
+ if (mSuggestions != null) {
+ cursor = mSuggestions!!.getResult()
+ }
+ changeSuggestions(cursor)
+ }
+
+ /**
+ * Replace the cursor.
+ *
+ * This does not close the old cursor. Instead, all the cursors are closed in [.setSuggestions].
+ */
+ private fun changeSuggestions(newCursor: SuggestionCursor?) {
+ if (DBG) {
+ Log.d(TAG, "changeCursor(" + newCursor + ") count=" + (newCursor?.count ?: 0))
+ }
+ if (newCursor === currentSuggestions) {
+ if (newCursor != null) {
+ // Shortcuts may have changed without the cursor changing.
+ notifyDataSetChanged()
+ }
+ return
+ }
+ currentSuggestions = newCursor
+ if (currentSuggestions != null) {
+ notifyDataSetChanged()
+ } else {
+ notifyDataSetInvalidated()
+ }
+ }
+
+ @Override
+ override fun onSuggestionClicked(suggestionId: Long) {
+ if (isClosed) {
+ Log.w(TAG, "onSuggestionClicked after close")
+ } else if (mSuggestionClickListener != null) {
+ mSuggestionClickListener!!.onSuggestionClicked(this, suggestionId)
+ }
+ }
+
+ @Override
+ override fun onSuggestionQueryRefineClicked(suggestionId: Long) {
+ if (isClosed) {
+ Log.w(TAG, "onSuggestionQueryRefineClicked after close")
+ } else if (mSuggestionClickListener != null) {
+ mSuggestionClickListener!!.onSuggestionQueryRefineClicked(this, suggestionId)
+ }
+ }
+
+ @get:Override abstract override val listAdapter: A
+ protected abstract fun notifyDataSetInvalidated()
+ protected abstract fun notifyDataSetChanged()
+ private inner class MySuggestionsObserver : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ onSuggestionsChanged()
+ }
+ }
+
+ private inner class SuggestionViewClickListener(private val mSuggestionId: Long) :
+ View.OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ onSuggestionClicked(mSuggestionId)
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SuggestionsAdapter"
+ }
+
+ init {
+ mViewTypeMap = hashMapOf<String, Int>()
+ for (viewType in mViewFactory.suggestionViewTypes) {
+ if (!mViewTypeMap.containsKey(viewType)) {
+ mViewTypeMap.put(viewType, mViewTypeMap.size)
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.java b/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.java
deleted file mode 100644
index 8bbdfbf..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-
-import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
-import com.android.quicksearchbox.Suggestions;
-
-/**
- * Uses a {@link Suggestions} object to back a {@link SuggestionsView}.
- */
-public class SuggestionsListAdapter extends SuggestionsAdapterBase<ListAdapter> {
-
- private Adapter mAdapter;
-
- public SuggestionsListAdapter(SuggestionViewFactory viewFactory) {
- super(viewFactory);
- mAdapter = new Adapter();
- }
-
- @Override
- public boolean isEmpty() {
- return mAdapter.getCount() == 0;
- }
-
- @Override
- public SuggestionPosition getSuggestion(long suggestionId) {
- return new SuggestionPosition(getCurrentSuggestions(), (int) suggestionId);
- }
-
- @Override
- public BaseAdapter getListAdapter() {
- return mAdapter;
- }
-
- @Override
- public void notifyDataSetChanged() {
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void notifyDataSetInvalidated() {
- mAdapter.notifyDataSetInvalidated();
- }
-
- class Adapter extends BaseAdapter {
-
- @Override
- public int getCount() {
- SuggestionCursor s = getCurrentSuggestions();
- return s == null ? 0 : s.getCount();
- }
-
- @Override
- public Object getItem(int position) {
- return getSuggestion(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return SuggestionsListAdapter.this.getView(
- getCurrentSuggestions(), position, position, convertView, parent);
- }
-
- @Override
- public int getItemViewType(int position) {
- return getSuggestionViewType(getCurrentSuggestions(), position);
- }
-
- @Override
- public int getViewTypeCount() {
- return getSuggestionViewTypeCount();
- }
-
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt b/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt
new file mode 100644
index 0000000..1762341
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.ListAdapter
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+
+/** Uses a [Suggestions] object to back a [SuggestionsView]. */
+class SuggestionsListAdapter(viewFactory: SuggestionViewFactory?) :
+ SuggestionsAdapterBase<ListAdapter?>(viewFactory!!) {
+ private val mAdapter: SuggestionsListAdapter.Adapter
+
+ @get:Override
+ override val isEmpty: Boolean
+ get() = mAdapter.getCount() == 0
+
+ @Override
+ override fun getSuggestion(suggestionId: Long): SuggestionPosition {
+ return SuggestionPosition(currentSuggestions, suggestionId.toInt())
+ }
+
+ @get:Override
+ override val listAdapter: BaseAdapter
+ get() = mAdapter
+
+ @Override
+ public override fun notifyDataSetChanged() {
+ mAdapter.notifyDataSetChanged()
+ }
+
+ @Override
+ public override fun notifyDataSetInvalidated() {
+ mAdapter.notifyDataSetInvalidated()
+ }
+
+ internal inner class Adapter : BaseAdapter() {
+ @Override
+ override fun getCount(): Int {
+ val s: SuggestionCursor? = currentSuggestions
+ return s?.count ?: 0
+ }
+
+ @Override
+ override fun getItem(position: Int): Any? {
+ return getSuggestion(position)
+ }
+
+ @Override
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ @Override
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+ return this@SuggestionsListAdapter.getView(
+ currentSuggestions,
+ position,
+ position.toLong(),
+ convertView,
+ parent
+ )
+ }
+
+ @Override
+ override fun getItemViewType(position: Int): Int {
+ return getSuggestionViewType(currentSuggestions, position)
+ }
+
+ @Override
+ override fun getViewTypeCount(): Int {
+ return suggestionViewTypeCount
+ }
+ }
+
+ init {
+ mAdapter = Adapter()
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListView.java b/src/com/android/quicksearchbox/ui/SuggestionsListView.java
deleted file mode 100644
index a162f3a..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionsListView.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.ui;
-
-import android.view.View;
-import android.widget.AbsListView;
-
-/**
- * Interface for suggestions list UI views.
- */
-public interface SuggestionsListView<A> {
-
- /**
- * See {@link View#setOnKeyListener}.
- */
- void setOnKeyListener(View.OnKeyListener l);
-
- /**
- * See {@link AbsListView#setOnScrollListener}.
- */
- void setOnScrollListener(AbsListView.OnScrollListener l);
-
- /**
- * See {@link View#setOnFocusChangeListener}.
- */
- void setOnFocusChangeListener(View.OnFocusChangeListener l);
-
- /**
- * See {@link View#setVisibility}.
- */
- void setVisibility(int visibility);
-
- /**
- * Sets the adapter for the list. See {@link AbsListView#setAdapter}
- */
- void setSuggestionsAdapter(SuggestionsAdapter<A> adapter);
-
- /**
- * Gets the adapter for the list.
- */
- SuggestionsAdapter<A> getSuggestionsAdapter();
-
- /**
- * Gets the ID of the currently selected item.
- */
- long getSelectedItemId();
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListView.kt b/src/com/android/quicksearchbox/ui/SuggestionsListView.kt
new file mode 100644
index 0000000..6b0b6c0
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsListView.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.view.View
+import android.widget.AbsListView
+
+/** Interface for suggestions list UI views. */
+interface SuggestionsListView<A> {
+ /** See [View.setOnKeyListener]. */
+ fun setOnKeyListener(l: View.OnKeyListener?)
+
+ /** See [AbsListView.setOnScrollListener]. */
+ fun setOnScrollListener(l: AbsListView.OnScrollListener?)
+
+ /** See [View.setOnFocusChangeListener]. */
+ fun setOnFocusChangeListener(l: View.OnFocusChangeListener?)
+
+ /** See [View.setVisibility]. */
+ fun setVisibility(visibility: Int)
+
+ /** Sets the adapter for the list. See [AbsListView.setAdapter] */
+ fun setSuggestionsAdapter(adapter: SuggestionsAdapter<A?>?)
+
+ /** Gets the adapter for the list. */
+ fun getSuggestionsAdapter(): SuggestionsAdapter<A?>?
+
+ /** Gets the ID of the currently selected item. */
+ fun getSelectedItemId(): Long
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsView.java b/src/com/android/quicksearchbox/ui/SuggestionsView.java
deleted file mode 100644
index 51deb67..0000000
--- a/src/com/android/quicksearchbox/ui/SuggestionsView.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-import com.android.quicksearchbox.SuggestionPosition;
-
-/**
- * Holds a list of suggestions.
- */
-public class SuggestionsView extends ListView implements SuggestionsListView<ListAdapter> {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SuggestionsView";
-
- private SuggestionsAdapter<ListAdapter> mSuggestionsAdapter;
-
- public SuggestionsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setSuggestionsAdapter(SuggestionsAdapter<ListAdapter> adapter) {
- super.setAdapter(adapter == null ? null : adapter.getListAdapter());
- mSuggestionsAdapter = adapter;
- }
-
- @Override
- public SuggestionsAdapter<ListAdapter> getSuggestionsAdapter() {
- return mSuggestionsAdapter;
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- setItemsCanFocus(true);
- }
-
- /**
- * Gets the position of the selected suggestion.
- *
- * @return A 0-based index, or {@code -1} if no suggestion is selected.
- */
- public int getSelectedPosition() {
- return getSelectedItemPosition();
- }
-
- /**
- * Gets the selected suggestion.
- *
- * @return {@code null} if no suggestion is selected.
- */
- public SuggestionPosition getSelectedSuggestion() {
- return (SuggestionPosition) getSelectedItem();
- }
-
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsView.kt b/src/com/android/quicksearchbox/ui/SuggestionsView.kt
new file mode 100644
index 0000000..c13df95
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsView.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ListAdapter
+import android.widget.ListView
+import com.android.quicksearchbox.SuggestionPosition
+
+/** Holds a list of suggestions. */
+class SuggestionsView(context: Context?, attrs: AttributeSet?) :
+ ListView(context, attrs), SuggestionsListView<ListAdapter?> {
+ private var mSuggestionsAdapter: SuggestionsAdapter<ListAdapter?>? = null
+
+ @Override
+ override fun setSuggestionsAdapter(adapter: SuggestionsAdapter<ListAdapter?>?) {
+ super.setAdapter(adapter?.listAdapter)
+ mSuggestionsAdapter = adapter
+ }
+
+ @Override
+ override fun getSuggestionsAdapter(): SuggestionsAdapter<ListAdapter?>? {
+ return mSuggestionsAdapter
+ }
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setItemsCanFocus(true)
+ }
+
+ /**
+ * Gets the position of the selected suggestion.
+ *
+ * @return A 0-based index, or `-1` if no suggestion is selected.
+ */
+ val selectedPosition: Int
+ get() = getSelectedItemPosition()
+
+ /**
+ * Gets the selected suggestion.
+ *
+ * @return `null` if no suggestion is selected.
+ */
+ val selectedSuggestion: SuggestionPosition
+ get() = getSelectedItem() as SuggestionPosition
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SuggestionsView"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.java b/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.java
deleted file mode 100644
index e01bd7e..0000000
--- a/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.ui;
-
-import com.android.quicksearchbox.QsbApplication;
-import com.android.quicksearchbox.R;
-import com.android.quicksearchbox.Suggestion;
-import com.android.quicksearchbox.SuggestionFormatter;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-
-/**
- * View for web search suggestions.
- */
-public class WebSearchSuggestionView extends BaseSuggestionView {
-
- private static final String VIEW_ID = "web_search";
-
- private final SuggestionFormatter mSuggestionFormatter;
-
- public WebSearchSuggestionView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mSuggestionFormatter = QsbApplication.get(context).getSuggestionFormatter();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- KeyListener keyListener = new KeyListener();
- setOnKeyListener(keyListener);
- mIcon2.setOnKeyListener(keyListener);
- mIcon2.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- onSuggestionQueryRefineClicked();
- }
- });
- mIcon2.setFocusable(true);
- }
-
- @Override
- public void bindAsSuggestion(Suggestion suggestion, String userQuery) {
- super.bindAsSuggestion(suggestion, userQuery);
-
- CharSequence text1 = mSuggestionFormatter.formatSuggestion(userQuery,
- suggestion.getSuggestionText1());
- setText1(text1);
- setIsHistorySuggestion(suggestion.isHistorySuggestion());
- }
-
- private void setIsHistorySuggestion(boolean isHistory) {
- if (isHistory) {
- mIcon1.setImageResource(R.drawable.ic_history_suggestion);
- mIcon1.setVisibility(VISIBLE);
- } else {
- mIcon1.setVisibility(INVISIBLE);
- }
- }
-
- private class KeyListener implements View.OnKeyListener {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- boolean consumed = false;
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && v != mIcon2) {
- consumed = mIcon2.requestFocus();
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && v == mIcon2) {
- consumed = requestFocus();
- }
- }
- return consumed;
- }
- }
-
- public static class Factory extends SuggestionViewInflater {
-
- public Factory(Context context) {
- super(VIEW_ID, WebSearchSuggestionView.class, R.layout.web_search_suggestion, context);
- }
-
- @Override
- public boolean canCreateView(Suggestion suggestion) {
- return suggestion.isWebSearchSuggestion();
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt b/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt
new file mode 100644
index 0000000..daebb46
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.KeyEvent
+import android.view.View
+import com.android.quicksearchbox.QsbApplication
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionFormatter
+
+/** View for web search suggestions. */
+class WebSearchSuggestionView(context: Context?, attrs: AttributeSet?) :
+ BaseSuggestionView(context, attrs) {
+ private val mSuggestionFormatter: SuggestionFormatter?
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val keyListener: WebSearchSuggestionView.KeyListener = KeyListener()
+ setOnKeyListener(keyListener)
+ mIcon2?.setOnKeyListener(keyListener)
+ mIcon2?.setOnClickListener(
+ object : OnClickListener {
+ override fun onClick(v: View?) {
+ onSuggestionQueryRefineClicked()
+ }
+ }
+ )
+ mIcon2?.setFocusable(true)
+ }
+
+ @Override
+ override fun bindAsSuggestion(suggestion: Suggestion?, userQuery: String?) {
+ super.bindAsSuggestion(suggestion, userQuery)
+ val text1 = mSuggestionFormatter?.formatSuggestion(userQuery, suggestion?.suggestionText1)
+ setText1(text1)
+ setIsHistorySuggestion(suggestion?.isHistorySuggestion)
+ }
+
+ private fun setIsHistorySuggestion(isHistory: Boolean?) {
+ if (isHistory == true) {
+ mIcon1?.setImageResource(R.drawable.ic_history_suggestion)
+ mIcon1?.setVisibility(VISIBLE)
+ } else {
+ mIcon1?.setVisibility(INVISIBLE)
+ }
+ }
+
+ private inner class KeyListener : View.OnKeyListener {
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ var consumed = false
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && v !== mIcon2) {
+ consumed = mIcon2!!.requestFocus()
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && v === mIcon2) {
+ consumed = requestFocus()
+ }
+ }
+ return consumed
+ }
+ }
+
+ class Factory(context: Context?) :
+ SuggestionViewInflater(
+ VIEW_ID,
+ WebSearchSuggestionView::class.java,
+ R.layout.web_search_suggestion,
+ context
+ ) {
+ @Override
+ override fun canCreateView(suggestion: Suggestion?): Boolean {
+ return suggestion!!.isWebSearchSuggestion
+ }
+ }
+
+ companion object {
+ private const val VIEW_ID = "web_search"
+ }
+
+ init {
+ mSuggestionFormatter = QsbApplication[context].suggestionFormatter
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/AsyncDataSetObservable.java b/src/com/android/quicksearchbox/util/AsyncDataSetObservable.java
deleted file mode 100644
index 5a75ff6..0000000
--- a/src/com/android/quicksearchbox/util/AsyncDataSetObservable.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.database.DataSetObservable;
-import android.os.Handler;
-
-/**
- * A version of {@link DataSetObservable} that performs callbacks on given {@link Handler}.
- */
-public class AsyncDataSetObservable extends DataSetObservable {
-
- private final Handler mHandler;
-
- private final Runnable mChangedRunnable = new Runnable() {
- public void run() {
- AsyncDataSetObservable.super.notifyChanged();
- }
- };
-
- private final Runnable mInvalidatedRunnable = new Runnable() {
- public void run() {
- AsyncDataSetObservable.super.notifyInvalidated();
- }
- };
-
- /**
- * @param handler Handler to run callbacks on.
- */
- public AsyncDataSetObservable(Handler handler) {
- mHandler = handler;
- }
-
- @Override
- public void notifyChanged() {
- if (mHandler == null) {
- super.notifyChanged();
- } else {
- mHandler.post(mChangedRunnable);
- }
- }
-
- @Override
- public void notifyInvalidated() {
- if (mHandler == null) {
- super.notifyInvalidated();
- } else {
- mHandler.post(mInvalidatedRunnable);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/AsyncDataSetObservable.kt b/src/com/android/quicksearchbox/util/AsyncDataSetObservable.kt
new file mode 100644
index 0000000..1732af8
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/AsyncDataSetObservable.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.database.DataSetObservable
+import android.os.Handler
+
+/** A version of [DataSetObservable] that performs callbacks on given [Handler]. */
+class AsyncDataSetObservable(handler: Handler?) : DataSetObservable() {
+ private val mHandler: Handler?
+ private val mChangedRunnable: Runnable =
+ object : Runnable {
+ override fun run() {
+ super@AsyncDataSetObservable.notifyChanged()
+ }
+ }
+ private val mInvalidatedRunnable: Runnable =
+ object : Runnable {
+ override fun run() {
+ super@AsyncDataSetObservable.notifyInvalidated()
+ }
+ }
+
+ @Override
+ override fun notifyChanged() {
+ if (mHandler == null) {
+ super.notifyChanged()
+ } else {
+ mHandler.post(mChangedRunnable)
+ }
+ }
+
+ @Override
+ override fun notifyInvalidated() {
+ if (mHandler == null) {
+ super.notifyInvalidated()
+ } else {
+ mHandler.post(mInvalidatedRunnable)
+ }
+ }
+
+ /** @param handler Handler to run callbacks on. */
+ init {
+ mHandler = handler
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/BarrierConsumer.java b/src/com/android/quicksearchbox/util/BarrierConsumer.java
deleted file mode 100644
index d02ae79..0000000
--- a/src/com/android/quicksearchbox/util/BarrierConsumer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A consumer that consumes a fixed number of values. When the expected number of values
- * has been consumed, further values are rejected.
- */
-public class BarrierConsumer<A> implements Consumer<A> {
-
- private final Lock mLock = new ReentrantLock();
- private final Condition mNotFull = mLock.newCondition();
-
- private final int mExpectedCount;
-
- // Set to null when getValues() returns.
- private ArrayList<A> mValues;
-
- /**
- * Constructs a new BarrierConsumer.
- *
- * @param expectedCount The number of values to consume.
- */
- public BarrierConsumer(int expectedCount) {
- mExpectedCount = expectedCount;
- mValues = new ArrayList<A>(expectedCount);
- }
-
- /**
- * Blocks until the expected number of results is available, or until the thread is
- * interrupted. This method should not be called multiple times.
- *
- * @return A list of values, never {@code null}.
- */
- public ArrayList<A> getValues() {
- mLock.lock();
- try {
- try {
- while (!isFull()) {
- mNotFull.await();
- }
- } catch (InterruptedException ex) {
- // Return the values that we've gotten so far
- }
- ArrayList<A> values = mValues;
- mValues = null; // mark that getValues() has returned
- return values;
- } finally {
- mLock.unlock();
- }
- }
-
- public boolean consume(A value) {
- mLock.lock();
- try {
- // Do nothing if getValues() has alrady returned,
- // or enough values have already been consumed
- if (mValues == null || isFull()) {
- return false;
- }
- mValues.add(value);
- if (isFull()) {
- // Wake up any thread waiting in getValues()
- mNotFull.signal();
- }
- return true;
- } finally {
- mLock.unlock();
- }
- }
-
- private boolean isFull() {
- return mValues.size() == mExpectedCount;
- }
-}
diff --git a/src/com/android/quicksearchbox/util/BarrierConsumer.kt b/src/com/android/quicksearchbox/util/BarrierConsumer.kt
new file mode 100644
index 0000000..83ff1d2
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/BarrierConsumer.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.util
+
+import java.util.ArrayList
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
+/**
+ * A consumer that consumes a fixed number of values. When the expected number of values has been
+ * consumed, further values are rejected.
+ */
+class BarrierConsumer<A>(private val mExpectedCount: Int) : Consumer<A> {
+ private val mLock: Lock = ReentrantLock()
+ private val mNotFull: Condition = mLock.newCondition()
+
+ // Set to null when getValues() returns.
+ private var mValues: ArrayList<A>?
+
+ /**
+ * Blocks until the expected number of results is available, or until the thread is interrupted.
+ * This method should not be called multiple times.
+ *
+ * @return A list of values, never `null`.
+ */
+ val values: ArrayList<A>?
+ get() {
+ mLock.lock()
+ return try {
+ try {
+ while (!isFull) {
+ mNotFull.await()
+ }
+ } catch (ex: InterruptedException) {
+ // Return the values that we've gotten so far
+ }
+ val values = mValues
+ mValues = null // mark that getValues() has returned
+ values
+ } finally {
+ mLock.unlock()
+ }
+ }
+
+ override fun consume(value: A): Boolean {
+ mLock.lock()
+ return try {
+ // Do nothing if getValues() has already returned,
+ // or enough values have already been consumed
+ if (mValues == null || isFull) {
+ return false
+ }
+ mValues?.add(value)
+ if (isFull) {
+ // Wake up any thread waiting in getValues()
+ mNotFull.signal()
+ }
+ true
+ } finally {
+ mLock.unlock()
+ }
+ }
+
+ private val isFull: Boolean
+ get() = mValues!!.size == mExpectedCount
+
+ /**
+ * Constructs a new BarrierConsumer.
+ *
+ * @param expectedCount The number of values to consume.
+ */
+ init {
+ mValues = ArrayList<A>(mExpectedCount)
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.java b/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.java
deleted file mode 100644
index 08d3ed7..0000000
--- a/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Executes NamedTasks in batches of a given size. Tasks are queued until
- * executeNextBatch is called.
- */
-public class BatchingNamedTaskExecutor implements NamedTaskExecutor {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.BatchingNamedTaskExecutor";
-
- private final NamedTaskExecutor mExecutor;
-
- /** Queue of tasks waiting to be dispatched to mExecutor **/
- private final ArrayList<NamedTask> mQueuedTasks = new ArrayList<NamedTask>();
-
- /**
- * Creates a new BatchingSourceTaskExecutor.
- *
- * @param executor A SourceTaskExecutor for actually executing the tasks.
- */
- public BatchingNamedTaskExecutor(NamedTaskExecutor executor) {
- mExecutor = executor;
- }
-
- public void execute(NamedTask task) {
- synchronized (mQueuedTasks) {
- if (DBG) Log.d(TAG, "Queuing " + task);
- mQueuedTasks.add(task);
- }
- }
-
- private void dispatch(NamedTask task) {
- if (DBG) Log.d(TAG, "Dispatching " + task);
- mExecutor.execute(task);
- }
-
- /**
- * Instructs the executor to submit the next batch of results.
- * @param batchSize the maximum number of entries to execute.
- */
- public void executeNextBatch(int batchSize) {
- NamedTask[] batch = new NamedTask[0];
- synchronized (mQueuedTasks) {
- int count = Math.min(mQueuedTasks.size(), batchSize);
- List<NamedTask> nextTasks = mQueuedTasks.subList(0, count);
- batch = nextTasks.toArray(batch);
- nextTasks.clear();
- if (DBG) Log.d(TAG, "Dispatching batch of " + count);
- }
-
- for (NamedTask task : batch) {
- dispatch(task);
- }
- }
-
- /**
- * Cancel any unstarted tasks running in this executor. This instance
- * should not be re-used after calling this method.
- */
- public void cancelPendingTasks() {
- synchronized (mQueuedTasks) {
- mQueuedTasks.clear();
- }
- }
-
- public void close() {
- cancelPendingTasks();
- mExecutor.close();
- }
-}
diff --git a/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.kt b/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.kt
new file mode 100644
index 0000000..a4fbc26
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/BatchingNamedTaskExecutor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.util.Log
+
+/**
+ * Executes NamedTasks in batches of a given size. Tasks are queued until executeNextBatch is
+ * called.
+ * @param executor A SourceTaskExecutor for actually executing the tasks.
+ */
+class BatchingNamedTaskExecutor(private val mExecutor: NamedTaskExecutor) : NamedTaskExecutor {
+ /** Queue of tasks waiting to be dispatched to mExecutor */
+ private val mQueuedTasks: ArrayList<NamedTask?> = arrayListOf()
+ override fun execute(task: NamedTask?) {
+ synchronized(mQueuedTasks) {
+ if (DBG) Log.d(TAG, "Queuing $task")
+ mQueuedTasks.add(task)
+ }
+ }
+
+ private fun dispatch(task: NamedTask?) {
+ if (DBG) Log.d(TAG, "Dispatching $task")
+ mExecutor.execute(task)
+ }
+
+ /**
+ * Instructs the executor to submit the next batch of results.
+ * @param batchSize the maximum number of entries to execute.
+ */
+ fun executeNextBatch(batchSize: Int) {
+ var batch = arrayOfNulls<NamedTask?>(0)
+ synchronized(mQueuedTasks) {
+ val count: Int = Math.min(mQueuedTasks.size, batchSize)
+ val nextTasks: ArrayList<NamedTask?> = mQueuedTasks.subList(0, count) as ArrayList<NamedTask?>
+ batch = nextTasks.toArray(batch)
+ nextTasks.clear()
+ if (DBG) Log.d(TAG, "Dispatching batch of $count")
+ }
+ for (task in batch) {
+ dispatch(task)
+ }
+ }
+
+ /**
+ * Cancel any un-started tasks running in this executor. This instance should not be re-used after
+ * calling this method.
+ */
+ override fun cancelPendingTasks() {
+ synchronized(mQueuedTasks) { mQueuedTasks.clear() }
+ }
+
+ override fun close() {
+ cancelPendingTasks()
+ mExecutor.close()
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.BatchingNamedTaskExecutor"
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/CachedLater.java b/src/com/android/quicksearchbox/util/CachedLater.java
deleted file mode 100644
index 49e86ba..0000000
--- a/src/com/android/quicksearchbox/util/CachedLater.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Abstract base class for a one-place cache that holds a value that is produced
- * asynchronously.
- *
- * @param <A> The type of the data held in the cache.
- */
-public abstract class CachedLater<A> implements NowOrLater<A> {
-
- private static final String TAG = "QSB.AsyncCache";
- private static final boolean DBG = false;
-
- private final Object mLock = new Object();
-
- private A mValue;
-
- private boolean mCreating;
- private boolean mValid;
-
- private List<Consumer<? super A>> mWaitingConsumers;
-
- /**
- * Creates the object to store in the cache. This method must call
- * {@link #store} when it's done.
- * This method must not block.
- */
- protected abstract void create();
-
- /**
- * Saves a new value to the cache.
- */
- protected void store(A value) {
- if (DBG) Log.d(TAG, "store()");
- List<Consumer<? super A>> waitingConsumers;
- synchronized (mLock) {
- mValue = value;
- mValid = true;
- mCreating = false;
- waitingConsumers = mWaitingConsumers;
- mWaitingConsumers = null;
- }
- if (waitingConsumers != null) {
- for (Consumer<? super A> consumer : waitingConsumers) {
- if (DBG) Log.d(TAG, "Calling consumer: " + consumer);
- consumer.consume(value);
- }
- }
- }
-
- /**
- * Gets the value.
- *
- * @param consumer A consumer that will be given the cached value.
- * The consumer may be called synchronously, or asynchronously on
- * an unspecified thread.
- */
- public void getLater(Consumer<? super A> consumer) {
- if (DBG) Log.d(TAG, "getLater()");
- boolean valid;
- A value;
- synchronized (mLock) {
- valid = mValid;
- value = mValue;
- if (!valid) {
- if (mWaitingConsumers == null) {
- mWaitingConsumers = new ArrayList<Consumer<? super A>>();
- }
- mWaitingConsumers.add(consumer);
- }
- }
- if (valid) {
- if (DBG) Log.d(TAG, "valid, calling consumer synchronously");
- consumer.consume(value);
- } else {
- boolean create = false;
- synchronized (mLock) {
- if (!mCreating) {
- mCreating = true;
- create = true;
- }
- }
- if (create) {
- if (DBG) Log.d(TAG, "not valid, calling create()");
- create();
- } else {
- if (DBG) Log.d(TAG, "not valid, already creating");
- }
- }
- }
-
- /**
- * Clears the cache.
- */
- public void clear() {
- if (DBG) Log.d(TAG, "clear()");
- synchronized (mLock) {
- mValue = null;
- mValid = false;
- }
- }
-
- public boolean haveNow() {
- synchronized (mLock) {
- return mValid;
- }
- }
-
- public synchronized A getNow() {
- synchronized (mLock) {
- if (!haveNow()) {
- throw new IllegalStateException("getNow() called when haveNow() is false");
- }
- return mValue;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/CachedLater.kt b/src/com/android/quicksearchbox/util/CachedLater.kt
new file mode 100644
index 0000000..a198683
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/CachedLater.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.util.Log
+import kotlin.collections.MutableList
+
+/**
+ * Abstract base class for a one-place cache that holds a value that is produced asynchronously.
+ *
+ * @param <A> The type of the data held in the cache.
+ */
+abstract class CachedLater<A> : NowOrLater<A> {
+ private val mLock: Any = Any()
+ private var mValue: A? = null
+ private var mCreating = false
+ private var mValid = false
+ private var mWaitingConsumers: MutableList<Consumer<in A>>? = null
+
+ /**
+ * Creates the object to store in the cache. This method must call [.store] when it's done. This
+ * method must not block.
+ */
+ protected abstract fun create()
+
+ /** Saves a new value to the cache. */
+ protected fun store(value: A) {
+ if (DBG) Log.d(TAG, "store()")
+ var waitingConsumers: MutableList<Consumer<in A>>?
+ synchronized(mLock) {
+ mValue = value
+ mValid = true
+ mCreating = false
+ waitingConsumers = mWaitingConsumers
+ mWaitingConsumers = null
+ }
+ if (waitingConsumers != null) {
+ for (consumer in waitingConsumers!!) {
+ if (DBG) Log.d(TAG, "Calling consumer: $consumer")
+ consumer.consume(value)
+ }
+ }
+ }
+
+ /**
+ * Gets the value.
+ *
+ * @param consumer A consumer that will be given the cached value. The consumer may be called
+ * synchronously, or asynchronously on an unspecified thread.
+ */
+ override fun getLater(consumer: Consumer<in A>?) {
+ if (DBG) Log.d(TAG, "getLater()")
+ var valid: Boolean
+ var value: A?
+ synchronized(mLock) {
+ valid = mValid
+ value = mValue
+ if (!valid) {
+ if (mWaitingConsumers == null) {
+ mWaitingConsumers = mutableListOf()
+ }
+ mWaitingConsumers?.add(consumer!!)
+ }
+ }
+ if (valid) {
+ if (DBG) Log.d(TAG, "valid, calling consumer synchronously")
+ consumer!!.consume(value!!)
+ } else {
+ var create = false
+ synchronized(mLock) {
+ if (!mCreating) {
+ mCreating = true
+ create = true
+ }
+ }
+ if (create) {
+ if (DBG) Log.d(TAG, "not valid, calling create()")
+ create()
+ } else {
+ if (DBG) Log.d(TAG, "not valid, already creating")
+ }
+ }
+ }
+
+ /** Clears the cache. */
+ fun clear() {
+ if (DBG) Log.d(TAG, "clear()")
+ synchronized(mLock) {
+ mValue = null
+ mValid = false
+ }
+ }
+
+ override fun haveNow(): Boolean {
+ synchronized(mLock) {
+ return mValid
+ }
+ }
+
+ @get:Synchronized
+ override val now: A
+ get() {
+ synchronized(mLock) {
+ if (!haveNow()) {
+ throw IllegalStateException("getNow() called when haveNow() is false")
+ }
+ return mValue!!
+ }
+ }
+
+ companion object {
+ private const val TAG = "QSB.AsyncCache"
+ private const val DBG = false
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/Consumer.java b/src/com/android/quicksearchbox/util/Consumer.kt
index 942b5dc..185eaa2 100644
--- a/src/com/android/quicksearchbox/util/Consumer.java
+++ b/src/com/android/quicksearchbox/util/Consumer.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,22 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.quicksearchbox.util;
+package com.android.quicksearchbox.util
/**
* Interface for data consumers.
*
- * @param <A> The type of data to consume.
+ * @param <A> The type of data to consume. </A>
*/
-public interface Consumer<A> {
-
- /**
- * Consumes a value.
- *
- * @param value The value to consume.
- * @return {@code true} if the value was accepted, {@code false} otherwise.
- */
- boolean consume(A value);
-
+interface Consumer<A> {
+ /**
+ * Consumes a value.
+ *
+ * @param value The value to consume.
+ * @return `true` if the value was accepted, `false` otherwise.
+ */
+ fun consume(value: A): Boolean
}
diff --git a/src/com/android/quicksearchbox/util/Consumers.java b/src/com/android/quicksearchbox/util/Consumers.java
deleted file mode 100644
index 52a97b1..0000000
--- a/src/com/android/quicksearchbox/util/Consumers.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.os.Handler;
-
-/**
- * Consumer utilities.
- */
-public class Consumers {
-
- private Consumers() {}
-
- public static <A extends QuietlyCloseable> void consumeCloseable(Consumer<A> consumer,
- A value) {
- boolean accepted = false;
- try {
- accepted = consumer.consume(value);
- } finally {
- if (!accepted && value != null) value.close();
- }
- }
-
- public static <A> void consumeAsync(Handler handler,
- final Consumer<A> consumer, final A value) {
- if (handler == null) {
- consumer.consume(value);
- } else {
- handler.post(new Runnable() {
- public void run() {
- consumer.consume(value);
- }
- });
- }
- }
-
- public static <A extends QuietlyCloseable> void consumeCloseableAsync(Handler handler,
- final Consumer<A> consumer, final A value) {
- if (handler == null) {
- consumeCloseable(consumer, value);
- } else {
- handler.post(new Runnable() {
- public void run() {
- consumeCloseable(consumer, value);
- }
- });
- }
- }
-
- public static <A> Consumer<A> createAsyncConsumer(
- final Handler handler, final Consumer<A> consumer) {
- return new Consumer<A>() {
- public boolean consume(A value) {
- consumeAsync(handler, consumer, value);
- return true;
- }
- };
- }
-
- public static <A extends QuietlyCloseable> Consumer<A> createAsyncCloseableConsumer(
- final Handler handler, final Consumer<A> consumer) {
- return new Consumer<A>() {
- public boolean consume(A value) {
- consumeCloseableAsync(handler, consumer, value);
- return true;
- }
- };
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/Consumers.kt b/src/com/android/quicksearchbox/util/Consumers.kt
new file mode 100644
index 0000000..481d24c
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/Consumers.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.os.Handler
+
+/** Consumer utilities. */
+object Consumers {
+ @JvmStatic
+ fun <A : QuietlyCloseable?> consumeCloseable(consumer: Consumer<A>?, value: A?) {
+ var accepted = false
+ try {
+ accepted = consumer!!.consume(value!!)
+ } finally {
+ if (!accepted && value != null) value.close()
+ }
+ }
+
+ @JvmStatic
+ fun <A> consumeAsync(handler: Handler?, consumer: Consumer<A?>?, value: A?) {
+ if (handler == null) {
+ consumer?.consume(value)
+ } else {
+ handler.post(
+ object : Runnable {
+ override fun run() {
+ consumer?.consume(value)
+ }
+ }
+ )
+ }
+ }
+
+ @JvmStatic
+ fun <A : QuietlyCloseable?> consumeCloseableAsync(
+ handler: Handler?,
+ consumer: Consumer<A>?,
+ value: A?
+ ) {
+ if (handler == null) {
+ consumeCloseable(consumer, value)
+ } else {
+ handler.post(
+ object : Runnable {
+ override fun run() {
+ consumeCloseable(consumer, value)
+ }
+ }
+ )
+ }
+ }
+
+ fun <A> createAsyncConsumer(handler: Handler?, consumer: Consumer<A?>?): Consumer<A?> {
+ return object : Consumer<A?> {
+ override fun consume(value: A?): Boolean {
+ consumeAsync(handler, consumer, value)
+ return true
+ }
+ }
+ }
+
+ fun <A : QuietlyCloseable?> createAsyncCloseableConsumer(
+ handler: Handler?,
+ consumer: Consumer<A>?
+ ): Consumer<A?> {
+ return object : Consumer<A?> {
+ override fun consume(value: A?): Boolean {
+ consumeCloseableAsync(handler, consumer, value)
+ return true
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/Factory.java b/src/com/android/quicksearchbox/util/Factory.kt
index 8aebe5c..9c25ff5 100644
--- a/src/com/android/quicksearchbox/util/Factory.java
+++ b/src/com/android/quicksearchbox/util/Factory.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.quicksearchbox.util
-package com.android.quicksearchbox.util;
-
-public interface Factory<A> {
-
- A create();
-
+interface Factory<A> {
+ fun create(): A
}
diff --git a/src/com/android/quicksearchbox/util/HttpHelper.java b/src/com/android/quicksearchbox/util/HttpHelper.java
deleted file mode 100644
index f300db4..0000000
--- a/src/com/android/quicksearchbox/util/HttpHelper.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * An interface that can issue HTTP GET / POST requests
- * with timeouts.
- */
-public interface HttpHelper {
-
- public String get(GetRequest request) throws IOException, HttpException;
-
- public String get(String url, Map<String,String> requestHeaders)
- throws IOException, HttpException;
-
- public String post(PostRequest request) throws IOException, HttpException;
-
- public String post(String url, Map<String,String> requestHeaders, String content)
- throws IOException, HttpException;
-
- public void setConnectTimeout(int timeoutMillis);
-
- public void setReadTimeout(int timeoutMillis);
-
- public static class GetRequest {
- private String mUrl;
- private Map<String,String> mHeaders;
-
- /**
- * Creates a new request.
- */
- public GetRequest() {
- }
-
- /**
- * Creates a new request.
- *
- * @param url Request URI.
- */
- public GetRequest(String url) {
- mUrl = url;
- }
-
- /**
- * Gets the request URI.
- */
- public String getUrl() {
- return mUrl;
- }
- /**
- * Sets the request URI.
- */
- public void setUrl(String url) {
- mUrl = url;
- }
-
- /**
- * Gets the request headers.
- *
- * @return The response headers. May return {@code null} if no headers are set.
- */
- public Map<String, String> getHeaders() {
- return mHeaders;
- }
-
- /**
- * Sets a request header.
- *
- * @param name Header name.
- * @param value Header value.
- */
- public void setHeader(String name, String value) {
- if (mHeaders == null) {
- mHeaders = new HashMap<String,String>();
- }
- mHeaders.put(name, value);
- }
- }
-
- public static class PostRequest extends GetRequest {
-
- private String mContent;
-
- public PostRequest() {
- }
-
- public PostRequest(String url) {
- super(url);
- }
-
- public void setContent(String content) {
- mContent = content;
- }
-
- public String getContent() {
- return mContent;
- }
- }
-
- /**
- * A HTTP exception.
- */
- public static class HttpException extends IOException {
- private final int mStatusCode;
- private final String mReasonPhrase;
-
- public HttpException(int statusCode, String reasonPhrase) {
- super(statusCode + " " + reasonPhrase);
- mStatusCode = statusCode;
- mReasonPhrase = reasonPhrase;
- }
-
- /**
- * Gets the HTTP response status code.
- */
- public int getStatusCode() {
- return mStatusCode;
- }
-
- /**
- * Gets the HTTP response reason phrase.
- */
- public String getReasonPhrase() {
- return mReasonPhrase;
- }
- }
-
- /**
- * An interface for URL rewriting.
- */
- public static interface UrlRewriter {
- public String rewrite(String url);
- }
-}
diff --git a/src/com/android/quicksearchbox/util/HttpHelper.kt b/src/com/android/quicksearchbox/util/HttpHelper.kt
new file mode 100644
index 0000000..0daf8d0
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/HttpHelper.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.util
+
+import java.io.IOException
+
+/** An interface that can issue HTTP GET / POST requests with timeouts. */
+interface HttpHelper {
+ @Throws(IOException::class, HttpException::class) operator fun get(request: GetRequest?): String?
+
+ @Throws(IOException::class, HttpException::class)
+ operator fun get(url: String?, requestHeaders: MutableMap<String, String>?): String?
+
+ @Throws(IOException::class, HttpException::class) fun post(request: PostRequest?): String?
+
+ @Throws(IOException::class, HttpException::class)
+ fun post(url: String?, requestHeaders: MutableMap<String, String>?, content: String?): String?
+ fun setConnectTimeout(timeoutMillis: Int)
+ fun setReadTimeout(timeoutMillis: Int)
+ open class GetRequest {
+ /** Gets the request URI. */
+ /** Sets the request URI. */
+ var url: String? = null
+
+ /**
+ * Gets the request headers.
+ *
+ * @return The response headers. May return `null` if no headers are set.
+ */
+ var headers: MutableMap<String, String>? = null
+ private set
+
+ /** Creates a new request. */
+ constructor()
+
+ /**
+ * Creates a new request.
+ *
+ * @param url Request URI.
+ */
+ constructor(url: String?) {
+ this.url = url
+ }
+
+ /**
+ * Sets a request header.
+ *
+ * @param name Header name.
+ * @param value Header value.
+ */
+ fun setHeader(name: String, value: String) {
+ if (headers == null) {
+ headers = mutableMapOf()
+ }
+ headers?.put(name, value)
+ }
+ }
+
+ class PostRequest : GetRequest {
+ var content: String? = null
+
+ constructor()
+ constructor(url: String?) : super(url)
+ }
+
+ /** A HTTP exception. */
+ class HttpException(
+ /** Gets the HTTP response status code. */
+ val statusCode: Int,
+ /** Gets the HTTP response reason phrase. */
+ val reasonPhrase: String
+ ) : IOException("$statusCode $reasonPhrase")
+
+ /** An interface for URL rewriting. */
+ interface UrlRewriter {
+ fun rewrite(url: String): String
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/JavaNetHttpHelper.java b/src/com/android/quicksearchbox/util/JavaNetHttpHelper.java
deleted file mode 100644
index 5a0c8b9..0000000
--- a/src/com/android/quicksearchbox/util/JavaNetHttpHelper.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.os.Build;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Simple HTTP client API.
- */
-public class JavaNetHttpHelper implements HttpHelper {
- private static final String TAG = "QSB.JavaNetHttpHelper";
- private static final boolean DBG = false;
-
- private static final int BUFFER_SIZE = 1024 * 4;
- private static final String USER_AGENT_HEADER = "User-Agent";
- private static final String DEFAULT_CHARSET = "UTF-8";
-
- private int mConnectTimeout;
- private int mReadTimeout;
- private final String mUserAgent;
- private final HttpHelper.UrlRewriter mRewriter;
-
- /**
- * Creates a new HTTP helper.
- *
- * @param rewriter URI rewriter
- * @param userAgent User agent string, e.g. "MyApp/1.0".
- */
- public JavaNetHttpHelper(UrlRewriter rewriter, String userAgent) {
- mUserAgent = userAgent + " (" + Build.DEVICE + " " + Build.ID + ")";
- mRewriter = rewriter;
- }
-
- /**
- * Executes a GET request and returns the response content.
- *
- * @param request Request.
- * @return The response content. This is the empty string if the response
- * contained no content.
- * @throws IOException If an IO error occurs.
- * @throws HttpException If the response has a status code other than 200.
- */
- public String get(GetRequest request) throws IOException, HttpException {
- return get(request.getUrl(), request.getHeaders());
- }
-
- /**
- * Executes a GET request and returns the response content.
- *
- * @param url Request URI.
- * @param requestHeaders Request headers.
- * @return The response content. This is the empty string if the response
- * contained no content.
- * @throws IOException If an IO error occurs.
- * @throws HttpException If the response has a status code other than 200.
- */
- public String get(String url, Map<String,String> requestHeaders)
- throws IOException, HttpException {
- HttpURLConnection c = null;
- try {
- c = createConnection(url, requestHeaders);
- c.setRequestMethod("GET");
- c.connect();
- return getResponseFrom(c);
- } finally {
- if (c != null) {
- c.disconnect();
- }
- }
- }
-
- @Override
- public String post(PostRequest request) throws IOException, HttpException {
- return post(request.getUrl(), request.getHeaders(), request.getContent());
- }
-
- public String post(String url, Map<String,String> requestHeaders, String content)
- throws IOException, HttpException {
- HttpURLConnection c = null;
- try {
- if (requestHeaders == null) {
- requestHeaders = new HashMap<String, String>();
- }
- requestHeaders.put("Content-Length",
- Integer.toString(content == null ? 0 : content.length()));
- c = createConnection(url, requestHeaders);
- c.setDoOutput(content != null);
- c.setRequestMethod("POST");
- c.connect();
- if (content != null) {
- OutputStreamWriter writer = new OutputStreamWriter(c.getOutputStream());
- writer.write(content);
- writer.close();
- }
- return getResponseFrom(c);
- } finally {
- if (c != null) {
- c.disconnect();
- }
- }
- }
-
- private HttpURLConnection createConnection(String url, Map<String, String> headers)
- throws IOException, HttpException {
- URL u = new URL(mRewriter.rewrite(url));
- if (DBG) Log.d(TAG, "URL=" + url + " rewritten='" + u + "'");
- HttpURLConnection c = (HttpURLConnection) u.openConnection();
- if (headers != null) {
- for (Map.Entry<String,String> e : headers.entrySet()) {
- String name = e.getKey();
- String value = e.getValue();
- if (DBG) Log.d(TAG, " " + name + ": " + value);
- c.addRequestProperty(name, value);
- }
- }
- c.addRequestProperty(USER_AGENT_HEADER, mUserAgent);
- if (mConnectTimeout != 0) {
- c.setConnectTimeout(mConnectTimeout);
- }
- if (mReadTimeout != 0) {
- c.setReadTimeout(mReadTimeout);
- }
- return c;
- }
-
- private String getResponseFrom(HttpURLConnection c) throws IOException, HttpException {
- if (c.getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new HttpException(c.getResponseCode(), c.getResponseMessage());
- }
- if (DBG) {
- Log.d(TAG, "Content-Type: " + c.getContentType() + " (assuming " +
- DEFAULT_CHARSET + ")");
- }
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(c.getInputStream(), DEFAULT_CHARSET));
- StringBuilder string = new StringBuilder();
- char[] chars = new char[BUFFER_SIZE];
- int bytes;
- while ((bytes = reader.read(chars)) != -1) {
- string.append(chars, 0, bytes);
- }
- return string.toString();
- }
-
- public void setConnectTimeout(int timeoutMillis) {
- mConnectTimeout = timeoutMillis;
- }
-
- public void setReadTimeout(int timeoutMillis) {
- mReadTimeout = timeoutMillis;
- }
-
- /**
- * A Url rewriter that does nothing, i.e., returns the
- * url that is passed to it.
- */
- public static class PassThroughRewriter implements UrlRewriter {
- @Override
- public String rewrite(String url) {
- return url;
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/util/JavaNetHttpHelper.kt b/src/com/android/quicksearchbox/util/JavaNetHttpHelper.kt
new file mode 100644
index 0000000..06a45d1
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/JavaNetHttpHelper.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.os.Build
+import android.util.Log
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
+import java.io.OutputStreamWriter
+import java.net.HttpURLConnection
+import java.net.URL
+
+/** Simple HTTP client API. */
+class JavaNetHttpHelper(rewriter: HttpHelper.UrlRewriter, userAgent: String) : HttpHelper {
+ private var mConnectTimeout = 0
+ private var mReadTimeout = 0
+ private val mUserAgent: String
+ private val mRewriter: HttpHelper.UrlRewriter
+
+ /**
+ * Executes a GET request and returns the response content.
+ *
+ * @param request Request.
+ * @return The response content. This is the empty string if the response contained no content.
+ * @throws IOException If an IO error occurs.
+ * @throws HttpException If the response has a status code other than 200.
+ */
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ override operator fun get(request: HttpHelper.GetRequest?): String? {
+ return get(request?.url, request?.headers)
+ }
+
+ /**
+ * Executes a GET request and returns the response content.
+ *
+ * @param url Request URI.
+ * @param requestHeaders Request headers.
+ * @return The response content. This is the empty string if the response contained no content.
+ * @throws IOException If an IO error occurs.
+ * @throws HttpException If the response has a status code other than 200.
+ */
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ override operator fun get(url: String?, requestHeaders: MutableMap<String, String>?): String? {
+ var c: HttpURLConnection? = null
+ return try {
+ c = createConnection(url!!, requestHeaders)
+ c.setRequestMethod("GET")
+ c.connect()
+ getResponseFrom(c)
+ } finally {
+ if (c != null) {
+ c.disconnect()
+ }
+ }
+ }
+
+ @Override
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ override fun post(request: HttpHelper.PostRequest?): String? {
+ return post(request?.url, request?.headers, request?.content)
+ }
+
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ override fun post(
+ url: String?,
+ requestHeaders: MutableMap<String, String>?,
+ content: String?
+ ): String? {
+ var mRequestHeaders: MutableMap<String, String>? = requestHeaders
+ var c: HttpURLConnection? = null
+ return try {
+ if (mRequestHeaders == null) {
+ mRequestHeaders = mutableMapOf()
+ }
+ mRequestHeaders.put("Content-Length", Integer.toString(content?.length ?: 0))
+ c = createConnection(url!!, mRequestHeaders)
+ c.setDoOutput(content != null)
+ c.setRequestMethod("POST")
+ c.connect()
+ if (content != null) {
+ val writer = OutputStreamWriter(c.getOutputStream())
+ writer.write(content)
+ writer.close()
+ }
+ getResponseFrom(c)
+ } finally {
+ if (c != null) {
+ c.disconnect()
+ }
+ }
+ }
+
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ private fun createConnection(url: String, headers: Map<String, String>?): HttpURLConnection {
+ val u = URL(mRewriter.rewrite(url))
+ if (DBG) Log.d(TAG, "URL=$url rewritten='$u'")
+ val c: HttpURLConnection = u.openConnection() as HttpURLConnection
+ if (headers != null) {
+ for (e in headers.entries) {
+ val name: String = e.key
+ val value: String = e.value
+ if (DBG) Log.d(TAG, " $name: $value")
+ c.addRequestProperty(name, value)
+ }
+ }
+ c.addRequestProperty(USER_AGENT_HEADER, mUserAgent)
+ if (mConnectTimeout != 0) {
+ c.setConnectTimeout(mConnectTimeout)
+ }
+ if (mReadTimeout != 0) {
+ c.setReadTimeout(mReadTimeout)
+ }
+ return c
+ }
+
+ @Throws(IOException::class, HttpHelper.HttpException::class)
+ private fun getResponseFrom(c: HttpURLConnection): String {
+ if (c.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ throw HttpHelper.HttpException(c.getResponseCode(), c.getResponseMessage())
+ }
+ if (DBG) {
+ Log.d(
+ TAG,
+ "Content-Type: " + c.getContentType().toString() + " (assuming " + DEFAULT_CHARSET + ")"
+ )
+ }
+ val reader = BufferedReader(InputStreamReader(c.getInputStream(), DEFAULT_CHARSET))
+ val string: StringBuilder = StringBuilder()
+ val chars = CharArray(BUFFER_SIZE)
+ var bytes: Int
+ while (reader.read(chars).also { bytes = it } != -1) {
+ string.append(chars, 0, bytes)
+ }
+ return string.toString()
+ }
+
+ override fun setConnectTimeout(timeoutMillis: Int) {
+ mConnectTimeout = timeoutMillis
+ }
+
+ override fun setReadTimeout(timeoutMillis: Int) {
+ mReadTimeout = timeoutMillis
+ }
+
+ /** A Url rewriter that does nothing, i.e., returns the url that is passed to it. */
+ class PassThroughRewriter : HttpHelper.UrlRewriter {
+ @Override
+ override fun rewrite(url: String): String {
+ return url
+ }
+ }
+
+ companion object {
+ private const val TAG = "QSB.JavaNetHttpHelper"
+ private const val DBG = false
+ private const val BUFFER_SIZE = 1024 * 4
+ private const val USER_AGENT_HEADER = "User-Agent"
+ private const val DEFAULT_CHARSET = "UTF-8"
+ }
+
+ /**
+ * Creates a new HTTP helper.
+ *
+ * @param rewriter URI rewriter
+ * @param userAgent User agent string, e.g. "MyApp/1.0".
+ */
+ init {
+ mUserAgent = userAgent + " (" + Build.DEVICE + " " + Build.ID + ")"
+ mRewriter = rewriter
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/LevenshteinDistance.java b/src/com/android/quicksearchbox/util/LevenshteinDistance.java
deleted file mode 100644
index ad86d41..0000000
--- a/src/com/android/quicksearchbox/util/LevenshteinDistance.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-/**
- * This class represents the matrix used in the Levenshtein distance algorithm, together
- * with the algorithm itself which operates on the matrix.
- *
- * We also track of the individual operations applied to transform the source string into the
- * target string so we can trace the path taken through the matrix afterwards, in order to
- * perform the formatting as required.
- */
-public class LevenshteinDistance {
- public static final int EDIT_DELETE = 0;
- public static final int EDIT_INSERT = 1;
- public static final int EDIT_REPLACE = 2;
- public static final int EDIT_UNCHANGED = 3;
-
- private final Token[] mSource;
- private final Token[] mTarget;
- private final int[][] mEditTypeTable;
- private final int[][] mDistanceTable;
-
- public LevenshteinDistance(Token[] source, Token[] target) {
- final int sourceSize = source.length;
- final int targetSize = target.length;
- final int[][] editTab = new int[sourceSize+1][targetSize+1];
- final int[][] distTab = new int[sourceSize+1][targetSize+1];
- editTab[0][0] = EDIT_UNCHANGED;
- distTab[0][0] = 0;
- for (int i = 1; i <= sourceSize; ++i) {
- editTab[i][0] = EDIT_DELETE;
- distTab[i][0] = i;
- }
- for (int i = 1; i <= targetSize; ++i) {
- editTab[0][i] = EDIT_INSERT;
- distTab[0][i] = i;
- }
- mEditTypeTable = editTab;
- mDistanceTable = distTab;
- mSource = source;
- mTarget = target;
- }
-
- /**
- * Implementation of Levenshtein distance algorithm.
- *
- * @return The Levenshtein distance.
- */
- public int calculate() {
- final Token[] src = mSource;
- final Token[] trg = mTarget;
- final int sourceLen = src.length;
- final int targetLen = trg.length;
- final int[][] distTab = mDistanceTable;
- final int[][] editTab = mEditTypeTable;
- for (int s = 1; s <= sourceLen; ++s) {
- Token sourceToken = src[s-1];
- for (int t = 1; t <= targetLen; ++t) {
- Token targetToken = trg[t-1];
- int cost = sourceToken.prefixOf(targetToken) ? 0 : 1;
-
- int distance = distTab[s-1][t] + 1;
- int type = EDIT_DELETE;
-
- int d = distTab[s][t - 1];
- if (d + 1 < distance ) {
- distance = d + 1;
- type = EDIT_INSERT;
- }
-
- d = distTab[s - 1][t - 1];
- if (d + cost < distance) {
- distance = d + cost;
- type = cost == 0 ? EDIT_UNCHANGED : EDIT_REPLACE;
- }
- distTab[s][t] = distance;
- editTab[s][t] = type;
- }
- }
- return distTab[sourceLen][targetLen];
- }
-
- /**
- * Gets the list of operations which were applied to each target token; {@link #calculate} must
- * have been called on this object before using this method.
- * @return A list of {@link EditOperation}s indicating the origin of each token in the target
- * string. The position of the token indicates the position in the source string of the
- * token that was unchanged/replaced, or the position in the source after which a target
- * token was inserted.
- */
- public EditOperation[] getTargetOperations() {
- final int trgLen = mTarget.length;
- final EditOperation[] ops = new EditOperation[trgLen];
- int targetPos = trgLen;
- int sourcePos = mSource.length;
- final int[][] editTab = mEditTypeTable;
- while (targetPos > 0) {
- int editType = editTab[sourcePos][targetPos];
- switch (editType) {
- case LevenshteinDistance.EDIT_DELETE:
- sourcePos--;
- break;
- case LevenshteinDistance.EDIT_INSERT:
- targetPos--;
- ops[targetPos] = new EditOperation(editType, sourcePos);
- break;
- case LevenshteinDistance.EDIT_UNCHANGED:
- case LevenshteinDistance.EDIT_REPLACE:
- targetPos--;
- sourcePos--;
- ops[targetPos] = new EditOperation(editType, sourcePos);
- break;
- }
- }
-
- return ops;
- }
-
- public static final class EditOperation {
- private final int mType;
- private final int mPosition;
- public EditOperation(int type, int position) {
- mType = type;
- mPosition = position;
- }
- public int getType() {
- return mType;
- }
- public int getPosition() {
- return mPosition;
- }
- }
-
- public static final class Token implements CharSequence {
- private final char[] mContainer;
- public final int mStart;
- public final int mEnd;
-
- public Token(char[] container, int start, int end) {
- mContainer = container;
- mStart = start;
- mEnd = end;
- }
-
- public int length() {
- return mEnd - mStart;
- }
-
- @Override
- public String toString() {
- // used in tests only.
- return subSequence(0, length());
- }
-
- public boolean prefixOf(final Token that) {
- final int len = length();
- if (len > that.length()) return false;
- final int thisStart = mStart;
- final int thatStart = that.mStart;
- final char[] thisContainer = mContainer;
- final char[] thatContainer = that.mContainer;
- for (int i = 0; i < len; ++i) {
- if (thisContainer[thisStart + i] != thatContainer[thatStart + i]) {
- return false;
- }
- }
- return true;
- }
-
- public char charAt(int index) {
- return mContainer[index + mStart];
- }
-
- public String subSequence(int start, int end) {
- return new String(mContainer, mStart + start, length());
- }
-
- }
-}
diff --git a/src/com/android/quicksearchbox/util/LevenshteinDistance.kt b/src/com/android/quicksearchbox/util/LevenshteinDistance.kt
new file mode 100644
index 0000000..f6035c7
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/LevenshteinDistance.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+/**
+ * This class represents the matrix used in the Levenshtein distance algorithm, together with the
+ * algorithm itself which operates on the matrix.
+ *
+ * We also track of the individual operations applied to transform the source string into the target
+ * string so we can trace the path taken through the matrix afterwards, in order to perform the
+ * formatting as required.
+ */
+class LevenshteinDistance(source: Array<Token?>?, target: Array<Token?>?) {
+ private val mSource: Array<Token?>?
+ private val mTarget: Array<Token?>?
+ private val mEditTypeTable: Array<IntArray>
+ private val mDistanceTable: Array<IntArray>
+
+ /**
+ * Implementation of Levenshtein distance algorithm.
+ *
+ * @return The Levenshtein distance.
+ */
+ fun calculate(): Int {
+ val src = mSource
+ val trg = mTarget
+ val sourceLen = src!!.size
+ val targetLen = trg!!.size
+ val distTab = mDistanceTable
+ val editTab = mEditTypeTable
+ for (s in 1..sourceLen) {
+ val sourceToken = src[s - 1]
+ for (t in 1..targetLen) {
+ val targetToken = trg[t - 1]
+ val cost = if (sourceToken?.prefixOf(targetToken) == true) 0 else 1
+ var distance = distTab[s - 1][t] + 1
+ var type: Int = EDIT_DELETE
+ var d = distTab[s][t - 1]
+ if (d + 1 < distance) {
+ distance = d + 1
+ type = EDIT_INSERT
+ }
+ d = distTab[s - 1][t - 1]
+ if (d + cost < distance) {
+ distance = d + cost
+ type = if (cost == 0) EDIT_UNCHANGED else EDIT_REPLACE
+ }
+ distTab[s][t] = distance
+ editTab[s][t] = type
+ }
+ }
+ return distTab[sourceLen][targetLen]
+ }
+
+ /**
+ * Gets the list of operations which were applied to each target token; [.calculate] must have
+ * been called on this object before using this method.
+ * @return A list of [EditOperation]s indicating the origin of each token in the target string.
+ * The position of the token indicates the position in the source string of the token that was
+ * unchanged/replaced, or the position in the source after which a target token was inserted.
+ */
+ val targetOperations: Array<EditOperation?>
+ get() {
+ val trgLen = mTarget!!.size
+ val ops = arrayOfNulls<EditOperation>(trgLen)
+ var targetPos = trgLen
+ var sourcePos = mSource!!.size
+ val editTab = mEditTypeTable
+ while (targetPos > 0) {
+ val editType = editTab[sourcePos][targetPos]
+ when (editType) {
+ EDIT_DELETE -> sourcePos--
+ EDIT_INSERT -> {
+ targetPos--
+ ops[targetPos] = EditOperation(editType, sourcePos)
+ }
+ EDIT_UNCHANGED,
+ EDIT_REPLACE -> {
+ targetPos--
+ sourcePos--
+ ops[targetPos] = EditOperation(editType, sourcePos)
+ }
+ }
+ }
+ return ops
+ }
+
+ class EditOperation(val type: Int, val position: Int)
+ class Token(private val mContainer: CharArray, val mStart: Int, val mEnd: Int) : CharSequence {
+ @get:Override
+ override val length: Int
+ get() = mEnd - mStart
+
+ @Override
+ override fun toString(): String {
+ // used in tests only.
+ return subSequence(0, length)
+ }
+
+ fun prefixOf(that: Token?): Boolean {
+ val len = length
+ if (len > that!!.length) return false
+ val thisStart = mStart
+ val thatStart: Int = that.mStart
+ val thisContainer = mContainer
+ val thatContainer: CharArray = that.mContainer
+ for (i in 0 until len) {
+ if (thisContainer[thisStart + i] != thatContainer[thatStart + i]) {
+ return false
+ }
+ }
+ return true
+ }
+
+ override fun get(index: Int): Char {
+ return mContainer[index + mStart]
+ }
+
+ override fun subSequence(startIndex: Int, endIndex: Int): String {
+ return String(mContainer, mStart + startIndex, length)
+ }
+ }
+
+ companion object {
+ const val EDIT_DELETE = 0
+ const val EDIT_INSERT = 1
+ const val EDIT_REPLACE = 2
+ const val EDIT_UNCHANGED = 3
+ }
+
+ init {
+ val sourceSize = source!!.size
+ val targetSize = target!!.size
+ val editTab = Array(sourceSize + 1) { IntArray(targetSize + 1) }
+ val distTab = Array(sourceSize + 1) { IntArray(targetSize + 1) }
+ editTab[0][0] = EDIT_UNCHANGED
+ distTab[0][0] = 0
+ for (i in 1..sourceSize) {
+ editTab[i][0] = EDIT_DELETE
+ distTab[i][0] = i
+ }
+ for (i in 1..targetSize) {
+ editTab[0][i] = EDIT_INSERT
+ distTab[0][i] = i
+ }
+ mEditTypeTable = editTab
+ mDistanceTable = distTab
+ mSource = source
+ mTarget = target
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/NamedTask.java b/src/com/android/quicksearchbox/util/NamedTask.kt
index fa11267..5d6b1a5 100644
--- a/src/com/android/quicksearchbox/util/NamedTask.java
+++ b/src/com/android/quicksearchbox/util/NamedTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,14 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.quicksearchbox.util
-package com.android.quicksearchbox.util;
-
-/**
- * A task that has a name.
- */
-public interface NamedTask extends Runnable {
-
- String getName();
-
+/** A task that has a name. */
+interface NamedTask : Runnable {
+ val name: String?
}
diff --git a/src/com/android/quicksearchbox/util/NamedTaskExecutor.java b/src/com/android/quicksearchbox/util/NamedTaskExecutor.java
deleted file mode 100644
index 67670af..0000000
--- a/src/com/android/quicksearchbox/util/NamedTaskExecutor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-/**
- * Runs tasks that have a name tag.
- */
-public interface NamedTaskExecutor {
-
- /**
- * Schedules a task for execution. Implementations should not throw
- * {@link java.util.concurrent.RejectedExecutionException} if the task
- * cannot be run. They should drop it silently instead.
- */
- void execute(NamedTask task);
-
- /**
- * Stops any unstarted tasks from running. Implementations of this method must be
- * idempotent.
- */
- void cancelPendingTasks();
-
- /**
- * Shuts down this executor, freeing any resources that it owns. The executor
- * may not be used after calling this method. Implementations of this method must be
- * idempotent.
- */
- void close();
-
-}
diff --git a/src/com/android/quicksearchbox/util/NamedTaskExecutor.kt b/src/com/android/quicksearchbox/util/NamedTaskExecutor.kt
new file mode 100644
index 0000000..c955244
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/NamedTaskExecutor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox.util
+
+/** Runs tasks that have a name tag. */
+interface NamedTaskExecutor {
+ /**
+ * Schedules a task for execution. Implementations should not throw
+ * [java.util.concurrent.RejectedExecutionException] if the task cannot be run. They should drop
+ * it silently instead.
+ */
+ fun execute(task: NamedTask?)
+
+ /** Stops any unstarted tasks from running. Implementations of this method must be idempotent. */
+ fun cancelPendingTasks()
+
+ /**
+ * Shuts down this executor, freeing any resources that it owns. The executor may not be used
+ * after calling this method. Implementations of this method must be idempotent.
+ */
+ fun close()
+}
diff --git a/src/com/android/quicksearchbox/util/NoOpConsumer.java b/src/com/android/quicksearchbox/util/NoOpConsumer.java
deleted file mode 100644
index bac138c..0000000
--- a/src/com/android/quicksearchbox/util/NoOpConsumer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import com.android.quicksearchbox.util.Consumer;
-
-/**
- * A Consumer that does nothing with the objects it receives.
- */
-public class NoOpConsumer<A> implements Consumer<A> {
- public boolean consume(A result) {
- // Tell the caller that we haven't taken ownership of this result.
- return false;
- }
-}
-
diff --git a/src/com/android/quicksearchbox/util/NoOpConsumer.kt b/src/com/android/quicksearchbox/util/NoOpConsumer.kt
new file mode 100644
index 0000000..36beb35
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/NoOpConsumer.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+/** A Consumer that does nothing with the objects it receives. */
+class NoOpConsumer<A> : Consumer<A> {
+ override fun consume(value: A): Boolean {
+ // Tell the caller that we haven't taken ownership of this result.
+ return false
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/Now.java b/src/com/android/quicksearchbox/util/Now.java
deleted file mode 100644
index 88328fd..0000000
--- a/src/com/android/quicksearchbox/util/Now.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.util;
-
-/**
- * A {@link NowOrLater} object that is always ready now.
- */
-public class Now<C> implements NowOrLater<C> {
-
- private final C mValue;
-
- public Now(C value) {
- mValue = value;
- }
-
- public void getLater(Consumer<? super C> consumer) {
- consumer.consume(getNow());
- }
-
- public C getNow() {
- return mValue;
- }
-
- public boolean haveNow() {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/Now.kt b/src/com/android/quicksearchbox/util/Now.kt
new file mode 100644
index 0000000..fb7a82d
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/Now.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+/** A [NowOrLater] object that is always ready now. */
+class Now<C>(override val now: C?) : NowOrLater<C?> {
+ override fun getLater(consumer: Consumer<in C?>?) {
+ consumer!!.consume(now)
+ }
+
+ override fun haveNow(): Boolean {
+ return true
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/NowOrLater.java b/src/com/android/quicksearchbox/util/NowOrLater.kt
index 6029ef6..97a8ac7 100644
--- a/src/com/android/quicksearchbox/util/NowOrLater.java
+++ b/src/com/android/quicksearchbox/util/NowOrLater.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,31 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quicksearchbox.util;
+package com.android.quicksearchbox.util
/**
* Interface for an object that may be constructed asynchronously. In cases when the object is ready
* (on constructible) immediately, it provides synchronous access to it. Otherwise, the object can
- * be sent to a {@link Consumer} later.
+ * be sent to a [Consumer] later.
*/
-public interface NowOrLater<C> {
+interface NowOrLater<C> {
+ /** Indicates if the object is ready (or constructible synchronously). */
+ fun haveNow(): Boolean
- /**
- * Indicates if the object is ready (or constructible synchronously).
- */
- boolean haveNow();
-
- /**
- * Gets the object now. Should only be called if {@link #haveNow()} returns {@code true},
- * otherwise an {@link IllegalStateException} will be thrown.
- */
- C getNow();
-
- /**
- * Request the object asynchronously. This can be called even if the object is ready now, in
- * which case the callback may be made in context. The thread on which the consumer is called
- * back depends on the implementation.
- */
- void getLater(Consumer<? super C> consumer);
+ /**
+ * Gets the object now. Should only be called if [.haveNow] returns `true`, otherwise an
+ * [IllegalStateException] will be thrown.
+ */
+ val now: C
+ /**
+ * Request the object asynchronously. This can be called even if the object is ready now, in which
+ * case the callback may be made in context. The thread on which the consumer is called back
+ * depends on the implementation.
+ */
+ fun getLater(consumer: Consumer<in C>?)
}
diff --git a/src/com/android/quicksearchbox/util/NowOrLaterWrapper.java b/src/com/android/quicksearchbox/util/NowOrLaterWrapper.java
deleted file mode 100644
index efe0901..0000000
--- a/src/com/android/quicksearchbox/util/NowOrLaterWrapper.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox.util;
-
-/**
- * {@link NowOrLater} class that converts from one type to another.
- */
-public abstract class NowOrLaterWrapper<A, B> implements NowOrLater<B> {
-
- private final NowOrLater<A> mWrapped;
-
- public NowOrLaterWrapper(NowOrLater<A> wrapped) {
- mWrapped = wrapped;
- }
-
- public void getLater(final Consumer<? super B> consumer) {
- mWrapped.getLater(new Consumer<A>(){
- public boolean consume(A value) {
- return consumer.consume(get(value));
- }});
- }
-
- public B getNow() {
- return get(mWrapped.getNow());
- }
-
- public boolean haveNow() {
- return mWrapped.haveNow();
- }
-
- /**
- * Perform the appropriate conversion. This will be called once for every call to
- * {@link #getLater(Consumer)} or {@link #getNow()}. The thread that it's called on will depend
- * on the behaviour of the wrapped object and the caller.
- */
- public abstract B get(A value);
-
-}
diff --git a/src/com/android/quicksearchbox/util/NowOrLaterWrapper.kt b/src/com/android/quicksearchbox/util/NowOrLaterWrapper.kt
new file mode 100644
index 0000000..1f9de29
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/NowOrLaterWrapper.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+/** [NowOrLater] class that converts from one type to another. */
+abstract class NowOrLaterWrapper<A, B>(private val mWrapped: NowOrLater<A>) : NowOrLater<B> {
+ override fun getLater(consumer: Consumer<in B>?) {
+ mWrapped.getLater(
+ object : Consumer<A> {
+ override fun consume(value: A): Boolean {
+ return consumer!!.consume(get(value))
+ }
+ }
+ )
+ }
+
+ override val now: B
+ get() = get(mWrapped.now)
+
+ override fun haveNow(): Boolean {
+ return mWrapped.haveNow()
+ }
+
+ /**
+ * Perform the appropriate conversion. This will be called once for every call to [.getLater] or
+ * [.getNow]. The thread that it's called on will depend on the behaviour of the wrapped object
+ * and the caller.
+ */
+ abstract operator fun get(value: A): B
+}
diff --git a/src/com/android/quicksearchbox/util/PerNameExecutor.java b/src/com/android/quicksearchbox/util/PerNameExecutor.java
deleted file mode 100644
index 3abd58f..0000000
--- a/src/com/android/quicksearchbox/util/PerNameExecutor.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-
-import java.util.HashMap;
-
-/**
- * Uses a separate executor for each task name.
- */
-public class PerNameExecutor implements NamedTaskExecutor {
-
- private final Factory<NamedTaskExecutor> mExecutorFactory;
- private HashMap<String, NamedTaskExecutor> mExecutors;
-
- /**
- * @param executorFactory Used to run the commands.
- */
- public PerNameExecutor(Factory<NamedTaskExecutor> executorFactory) {
- mExecutorFactory = executorFactory;
- }
-
- public synchronized void cancelPendingTasks() {
- if (mExecutors == null) return;
- for (NamedTaskExecutor executor : mExecutors.values()) {
- executor.cancelPendingTasks();
- }
- }
-
- public synchronized void close() {
- if (mExecutors == null) return;
- for (NamedTaskExecutor executor : mExecutors.values()) {
- executor.close();
- }
- }
-
- public synchronized void execute(NamedTask task) {
- if (mExecutors == null) {
- mExecutors = new HashMap<String, NamedTaskExecutor>();
- }
- String name = task.getName();
- NamedTaskExecutor executor = mExecutors.get(name);
- if (executor == null) {
- executor = mExecutorFactory.create();
- mExecutors.put(name, executor);
- }
- executor.execute(task);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/PerNameExecutor.kt b/src/com/android/quicksearchbox/util/PerNameExecutor.kt
new file mode 100644
index 0000000..cc0cf19
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/PerNameExecutor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import kotlin.collections.HashMap
+
+/**
+ * Uses a separate executor for each task name.
+ * @param executorFactory Used to run the commands.
+ */
+class PerNameExecutor(private val mExecutorFactory: Factory<NamedTaskExecutor>) :
+ NamedTaskExecutor {
+ private var mExecutors: HashMap<String, NamedTaskExecutor>? = null
+
+ @Synchronized
+ override fun cancelPendingTasks() {
+ if (mExecutors == null) return
+ for (executor in mExecutors!!.values) {
+ executor.cancelPendingTasks()
+ }
+ }
+
+ @Synchronized
+ override fun close() {
+ if (mExecutors == null) return
+ for (executor in mExecutors!!.values) {
+ executor.close()
+ }
+ }
+
+ @Synchronized
+ override fun execute(task: NamedTask?) {
+ if (mExecutors == null) {
+ mExecutors = HashMap<String, NamedTaskExecutor>()
+ }
+ val name: String? = task?.name
+ var executor: NamedTaskExecutor? = mExecutors?.get(name)
+ if (executor == null) {
+ executor = mExecutorFactory.create()
+ mExecutors?.put(name!!, executor)
+ }
+ executor.execute(task)
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/PriorityThreadFactory.java b/src/com/android/quicksearchbox/util/PriorityThreadFactory.java
deleted file mode 100644
index b75df0d..0000000
--- a/src/com/android/quicksearchbox/util/PriorityThreadFactory.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.os.Process;
-
-import java.util.concurrent.ThreadFactory;
-
-/**
- * A thread factory that creates threads with a given thread priority.
- */
-public class PriorityThreadFactory implements ThreadFactory {
-
- private final int mPriority;
-
- /**
- * Creates a new thread factory.
- *
- * @param priority The thread priority of the threads created by this factory.
- * For values, see {@link Process}.
- */
- public PriorityThreadFactory(int priority) {
- mPriority = priority;
- }
-
- public Thread newThread(Runnable r) {
- return new Thread(r) {
- @Override
- public void run() {
- Process.setThreadPriority(mPriority);
- super.run();
- }
- };
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/PriorityThreadFactory.kt b/src/com/android/quicksearchbox/util/PriorityThreadFactory.kt
new file mode 100644
index 0000000..bf4e8a3
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/PriorityThreadFactory.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.os.Process
+import java.util.concurrent.ThreadFactory
+
+/**
+ * A thread factory that creates threads with a given thread priority.
+ * @param priority The thread priority of the threads created by this factory. For values, see
+ * [Process].
+ */
+class PriorityThreadFactory(private val mPriority: Int) : ThreadFactory {
+ override fun newThread(r: Runnable?): Thread {
+ return object : Thread(r) {
+ @Override
+ override fun run() {
+ Process.setThreadPriority(mPriority)
+ super.run()
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/QuietlyCloseable.java b/src/com/android/quicksearchbox/util/QuietlyCloseable.kt
index d442f8f..c6f5558 100644
--- a/src/com/android/quicksearchbox/util/QuietlyCloseable.java
+++ b/src/com/android/quicksearchbox/util/QuietlyCloseable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.quicksearchbox.util
-package com.android.quicksearchbox.util;
-
-import java.io.Closeable;
-
-/**
- * Interface for closeable objects whose close method doesn't throw IOExceptions.
- */
-public interface QuietlyCloseable extends Closeable {
-
- void close();
+import java.io.Closeable
+/** Interface for closeable objects whose close method doesn't throw IOExceptions. */
+interface QuietlyCloseable : Closeable {
+ override fun close()
}
diff --git a/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.java b/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.java
deleted file mode 100644
index e4afecb..0000000
--- a/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.database.sqlite.SQLiteDatabase;
-
-/**
- * Abstract helper base class for asynchronous SQLite queries.
- *
- * @param <A> The type of the result of the query.
- */
-public abstract class SQLiteAsyncQuery<A> {
-
- /**
- * Performs a query and computes some value from the result
- *
- * @param db A readable database.
- * @return The result of the query.
- */
- protected abstract A performQuery(SQLiteDatabase db);
-
- /**
- * Runs the query against the database and passes the result to the consumer.
- */
- public void run(SQLiteDatabase db, Consumer<A> consumer) {
- A result = performQuery(db);
- consumer.consume(result);
- }
-}
diff --git a/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.kt b/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.kt
new file mode 100644
index 0000000..d9cb85e
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/SQLiteAsyncQuery.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.database.sqlite.SQLiteDatabase
+
+/**
+ * Abstract helper base class for asynchronous SQLite queries.
+ *
+ * @param <A> The type of the result of the query. </A>
+ */
+abstract class SQLiteAsyncQuery<A> {
+ /**
+ * Performs a query and computes some value from the result
+ *
+ * @param db A readable database.
+ * @return The result of the query.
+ */
+ protected abstract fun performQuery(db: SQLiteDatabase?): A
+
+ /** Runs the query against the database and passes the result to the consumer. */
+ fun run(db: SQLiteDatabase?, consumer: Consumer<A>) {
+ val result = performQuery(db)
+ consumer.consume(result)
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/SQLiteTransaction.java b/src/com/android/quicksearchbox/util/SQLiteTransaction.java
deleted file mode 100644
index aa423cd..0000000
--- a/src/com/android/quicksearchbox/util/SQLiteTransaction.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.database.sqlite.SQLiteDatabase;
-
-/**
- * Abstract helper base class for SQLite write transactions.
- */
-public abstract class SQLiteTransaction {
-
- /**
- * Executes the statements that form the transaction.
- *
- * @param db A writable database.
- * @return {@code true} if the transaction should be committed.
- */
- protected abstract boolean performTransaction(SQLiteDatabase db);
-
- /**
- * Runs the transaction against the database. The results are committed if
- * {@link #performTransaction(SQLiteDatabase)} completes normally and returns {@code true}.
- */
- public void run(SQLiteDatabase db) {
- db.beginTransaction();
- try {
- if (performTransaction(db)) {
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/util/SQLiteTransaction.kt b/src/com/android/quicksearchbox/util/SQLiteTransaction.kt
new file mode 100644
index 0000000..9932e2d
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/SQLiteTransaction.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.database.sqlite.SQLiteDatabase
+
+/** Abstract helper base class for SQLite write transactions. */
+abstract class SQLiteTransaction {
+ /**
+ * Executes the statements that form the transaction.
+ *
+ * @param db A writable database.
+ * @return `true` if the transaction should be committed.
+ */
+ protected abstract fun performTransaction(db: SQLiteDatabase?): Boolean
+
+ /**
+ * Runs the transaction against the database. The results are committed if [.performTransaction]
+ * completes normally and returns `true`.
+ */
+ fun run(db: SQLiteDatabase) {
+ db.beginTransaction()
+ try {
+ if (performTransaction(db)) {
+ db.setTransactionSuccessful()
+ }
+ } finally {
+ db.endTransaction()
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.java b/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.java
deleted file mode 100644
index be4012f..0000000
--- a/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.util.Log;
-
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-
-/**
- * Executor that uses a single thread and an unbounded work queue.
- */
-public class SingleThreadNamedTaskExecutor implements NamedTaskExecutor {
-
- private static final boolean DBG = false;
- private static final String TAG = "QSB.SingleThreadNamedTaskExecutor";
-
- private final LinkedBlockingQueue<NamedTask> mQueue;
- private final Thread mWorker;
- private volatile boolean mClosed = false;
-
- public SingleThreadNamedTaskExecutor(ThreadFactory threadFactory) {
- mQueue = new LinkedBlockingQueue<NamedTask>();
- mWorker = threadFactory.newThread(new Worker());
- mWorker.start();
- }
-
- public void cancelPendingTasks() {
- if (DBG) Log.d(TAG, "Cancelling " + mQueue.size() + " tasks: " + mWorker.getName());
- if (mClosed) {
- throw new IllegalStateException("cancelPendingTasks() after close()");
- }
- mQueue.clear();
- }
-
- public void close() {
- mClosed = true;
- mWorker.interrupt();
- mQueue.clear();
- }
-
- public void execute(NamedTask task) {
- if (mClosed) {
- throw new IllegalStateException("execute() after close()");
- }
- mQueue.add(task);
- }
-
- private class Worker implements Runnable {
- public void run() {
- try {
- loop();
- } finally {
- if (!mClosed) Log.w(TAG, "Worker exited before close");
- }
- }
-
- private void loop() {
- Thread currentThread = Thread.currentThread();
- String threadName = currentThread.getName();
- while (!mClosed) {
- NamedTask task;
- try {
- task = mQueue.take();
- } catch (InterruptedException ex) {
- continue;
- }
- currentThread.setName(threadName + " " + task.getName());
- try {
- if (DBG) Log.d(TAG, "Running task " + task.getName());
- task.run();
- if (DBG) Log.d(TAG, "Task " + task.getName() + " complete");
- } catch (RuntimeException ex) {
- Log.e(TAG, "Task " + task.getName() + " failed", ex);
- }
- }
- }
- }
-
- public static Factory<NamedTaskExecutor> factory(final ThreadFactory threadFactory) {
- return new Factory<NamedTaskExecutor>() {
- public NamedTaskExecutor create() {
- return new SingleThreadNamedTaskExecutor(threadFactory);
- }
- };
- }
-
-}
diff --git a/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.kt b/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.kt
new file mode 100644
index 0000000..ffe0b6e
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/SingleThreadNamedTaskExecutor.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.util.Log
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadFactory
+
+/** Executor that uses a single thread and an unbounded work queue. */
+class SingleThreadNamedTaskExecutor(threadFactory: ThreadFactory?) : NamedTaskExecutor {
+ private val mQueue: LinkedBlockingQueue<NamedTask>
+ private val mWorker: Thread
+
+ @Volatile private var mClosed = false
+ override fun cancelPendingTasks() {
+ if (DBG) Log.d(TAG, "Cancelling " + mQueue.size.toString() + " tasks: " + mWorker.name)
+ if (mClosed) {
+ throw IllegalStateException("cancelPendingTasks() after close()")
+ }
+ mQueue.clear()
+ }
+
+ override fun close() {
+ mClosed = true
+ mWorker.interrupt()
+ mQueue.clear()
+ }
+
+ override fun execute(task: NamedTask?) {
+ if (mClosed) {
+ throw IllegalStateException("execute() after close()")
+ }
+ mQueue.add(task)
+ }
+
+ private inner class Worker : Runnable {
+ override fun run() {
+ try {
+ loop()
+ } finally {
+ if (!mClosed) Log.w(TAG, "Worker exited before close")
+ }
+ }
+
+ private fun loop() {
+ val currentThread: Thread = Thread.currentThread()
+ val threadName: String = currentThread.getName()
+ while (!mClosed) {
+ val task: NamedTask =
+ try {
+ mQueue.take()
+ } catch (ex: InterruptedException) {
+ continue
+ }
+ currentThread.setName(threadName + " " + task.name)
+ try {
+ if (DBG) Log.d(TAG, "Running task " + task.name)
+ task.run()
+ if (DBG) Log.d(TAG, "Task " + task.name + " complete")
+ } catch (ex: RuntimeException) {
+ Log.e(TAG, "Task " + task.name + " failed", ex)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SingleThreadNamedTaskExecutor"
+ @JvmStatic
+ fun factory(threadFactory: ThreadFactory?): Factory<NamedTaskExecutor> {
+ return object : Factory<NamedTaskExecutor> {
+ override fun create(): NamedTaskExecutor {
+ return SingleThreadNamedTaskExecutor(threadFactory)
+ }
+ }
+ }
+ }
+
+ init {
+ mQueue = LinkedBlockingQueue<NamedTask>()
+ mWorker = threadFactory!!.newThread(Worker())
+ mWorker.start()
+ }
+}
diff --git a/src/com/android/quicksearchbox/util/Util.java b/src/com/android/quicksearchbox/util/Util.java
deleted file mode 100644
index 373d2af..0000000
--- a/src/com/android/quicksearchbox/util/Util.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quicksearchbox.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.util.Log;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * General utilities.
- */
-public class Util {
-
- private static final String TAG = "QSB.Util";
-
- public static <A> Set<A> setOfFirstN(List<A> list, int n) {
- int end = Math.min(list.size(), n);
- HashSet<A> set = new HashSet<A>(end);
- for (int i = 0; i < end; i++) {
- set.add(list.get(i));
- }
- return set;
- }
-
- public static Uri getResourceUri(Context packageContext, int res) {
- try {
- Resources resources = packageContext.getResources();
- return getResourceUri(resources, packageContext.getPackageName(), res);
- } catch (Resources.NotFoundException e) {
- Log.e(TAG, "Resource not found: " + res + " in " + packageContext.getPackageName());
- return null;
- }
- }
-
- public static Uri getResourceUri(Context context, ApplicationInfo appInfo, int res) {
- try {
- Resources resources = context.getPackageManager().getResourcesForApplication(appInfo);
- return getResourceUri(resources, appInfo.packageName, res);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Resources not found for " + appInfo.packageName);
- return null;
- } catch (Resources.NotFoundException e) {
- Log.e(TAG, "Resource not found: " + res + " in " + appInfo.packageName);
- return null;
- }
- }
-
- private static Uri getResourceUri(Resources resources, String appPkg, int res)
- throws Resources.NotFoundException {
- String resPkg = resources.getResourcePackageName(res);
- String type = resources.getResourceTypeName(res);
- String name = resources.getResourceEntryName(res);
- return makeResourceUri(appPkg, resPkg, type, name);
- }
-
- private static Uri makeResourceUri(String appPkg, String resPkg, String type, String name) {
- Uri.Builder uriBuilder = new Uri.Builder();
- uriBuilder.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE);
- uriBuilder.encodedAuthority(appPkg);
- uriBuilder.appendEncodedPath(type);
- if (!appPkg.equals(resPkg)) {
- uriBuilder.appendEncodedPath(resPkg + ":" + name);
- } else {
- uriBuilder.appendEncodedPath(name);
- }
- return uriBuilder.build();
- }
-}
diff --git a/src/com/android/quicksearchbox/util/Util.kt b/src/com/android/quicksearchbox/util/Util.kt
new file mode 100644
index 0000000..78b9e5e
--- /dev/null
+++ b/src/com/android/quicksearchbox/util/Util.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox.util
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.net.Uri
+import android.util.Log
+
+/** General utilities. */
+object Util {
+
+ private const val TAG = "QSB.Util"
+
+ fun <A> setOfFirstN(list: List<A>, n: Int): Set<A> {
+ val end: Int = Math.min(list.size, n)
+ val set: HashSet<A> = hashSetOf()
+ for (i in 0 until end) {
+ set.add(list[i])
+ }
+ return set
+ }
+
+ fun getResourceUri(packageContext: Context?, res: Int): Uri? {
+ return try {
+ val resources: Resources? = packageContext?.getResources()
+ getResourceUri(resources, packageContext?.getPackageName(), res)
+ } catch (e: Resources.NotFoundException) {
+ Log.e(TAG, "Resource not found: " + res + " in " + packageContext?.getPackageName())
+ null
+ }
+ }
+
+ fun getResourceUri(context: Context?, appInfo: ApplicationInfo?, res: Int): Uri? {
+ return try {
+ val resources: Resources? =
+ context?.getPackageManager()?.getResourcesForApplication(appInfo!!)
+ getResourceUri(resources, appInfo?.packageName, res)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Resources not found for " + appInfo?.packageName)
+ null
+ } catch (e: Resources.NotFoundException) {
+ Log.e(TAG, "Resource not found: " + res + " in " + appInfo?.packageName)
+ null
+ }
+ }
+
+ @Throws(Resources.NotFoundException::class)
+ private fun getResourceUri(resources: Resources?, appPkg: String?, res: Int): Uri {
+ val resPkg: String? = resources?.getResourcePackageName(res)
+ val type: String? = resources?.getResourceTypeName(res)
+ val name: String? = resources?.getResourceEntryName(res)
+ return makeResourceUri(appPkg, resPkg, type, name)
+ }
+
+ private fun makeResourceUri(appPkg: String?, resPkg: String?, type: String?, name: String?): Uri {
+ val uriBuilder: Uri.Builder = Uri.Builder()
+ uriBuilder.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ uriBuilder.encodedAuthority(appPkg)
+ uriBuilder.appendEncodedPath(type)
+ if (appPkg != resPkg) {
+ uriBuilder.appendEncodedPath("$resPkg:$name")
+ } else {
+ uriBuilder.appendEncodedPath(name)
+ }
+ return uriBuilder.build()
+ }
+}
diff --git a/tests/src/com/android/quicksearchbox/tests/CrashingIconProvider.java b/tests/src/com/android/quicksearchbox/tests/CrashingIconProvider.java
index c2162e9..b42ff93 100644
--- a/tests/src/com/android/quicksearchbox/tests/CrashingIconProvider.java
+++ b/tests/src/com/android/quicksearchbox/tests/CrashingIconProvider.java
@@ -22,6 +22,8 @@ import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.util.Arrays;
+
/**
* A content provider that crashes when something is requested.
*/
@@ -46,7 +48,10 @@ public class CrashingIconProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (DBG) Log.d(TAG, "delete(" + uri + ", " + selection + ", " + selectionArgs + ")");
+ if (DBG) {
+ Log.d(TAG, "delete(" + uri + ", " + selection + ", " +
+ Arrays.toString(selectionArgs) + ")");
+ }
throw new UnsupportedOperationException();
}