summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-09-08 16:56:09 -0700
committerXin Li <delphij@google.com>2020-09-08 16:56:09 -0700
commitfedfa990901039d259559cf9889539bfa2192fa9 (patch)
tree36fab9a862f8f64d327c308229dcc9196f5ebda1
parent9c7a808ea96fd072e1fd68b4bcd1733f8ff4a3b2 (diff)
parenta4636f52c31ebd907997b0faef28c1939b29b817 (diff)
downloadSettingsIntelligence-fedfa990901039d259559cf9889539bfa2192fa9.tar.gz
Merge Android R
Bug: 168057903 Merged-In: I2c3ca1f0794de3b0079b4715f7ce6d33cd786bdc Change-Id: Ic98265603b0e54f50c2acfab9e4e4149a090ef69
-rw-r--r--Android.bp2
-rw-r--r--AndroidManifest.xml2
-rw-r--r--OWNERS3
-rw-r--r--res/values/themes.xml3
-rw-r--r--res/xml/car_search_fragment.xml21
-rw-r--r--src/com/android/settings/intelligence/search/SearchActivity.java28
-rw-r--r--src/com/android/settings/intelligence/search/SearchCommon.java57
-rw-r--r--src/com/android/settings/intelligence/search/SearchFragment.java43
-rw-r--r--src/com/android/settings/intelligence/search/car/CarFeatureFactoryImpl.java36
-rw-r--r--src/com/android/settings/intelligence/search/car/CarIntentSearchViewHolder.java92
-rw-r--r--src/com/android/settings/intelligence/search/car/CarSearchFeatureProviderImpl.java163
-rw-r--r--src/com/android/settings/intelligence/search/car/CarSearchFragment.java269
-rw-r--r--src/com/android/settings/intelligence/search/car/CarSearchResultsAdapter.java122
-rw-r--r--src/com/android/settings/intelligence/search/car/CarSearchViewHolder.java50
-rw-r--r--src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java12
-rw-r--r--src/com/android/settings/intelligence/search/indexing/IndexData.java19
-rw-r--r--src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java10
-rw-r--r--src/com/android/settings/intelligence/search/indexing/car/CarDatabaseIndexingManager.java39
-rw-r--r--src/com/android/settings/intelligence/search/indexing/car/CarIndexData.java46
-rw-r--r--src/com/android/settings/intelligence/search/indexing/car/CarIndexDataConverter.java38
-rw-r--r--src/com/android/settings/intelligence/search/savedqueries/SavedQueryController.java24
-rw-r--r--src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryController.java133
-rw-r--r--src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryViewHolder.java44
-rw-r--r--src/com/android/settings/intelligence/utils/AsyncLoader.java2
24 files changed, 1199 insertions, 59 deletions
diff --git a/Android.bp b/Android.bp
index fc8062a..26c946d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,6 +22,7 @@ android_app {
privileged: true,
required: ["privapp_whitelist_com.android.settings.intelligence"],
+ libs: ["android.car-stubs"],
static_libs: [
"androidx.legacy_legacy-support-v4",
"androidx.legacy_legacy-support-v13",
@@ -30,6 +31,7 @@ android_app {
"androidx.preference_preference",
"androidx.recyclerview_recyclerview",
"androidx.legacy_legacy-preference-v14",
+ "car-ui-lib",
],
resource_dirs: ["res"],
srcs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5b0bd6a..8c876bb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />
+ <uses-sdk android:targetSdkVersion="29" />
+
<application
android:label="@string/app_name_settings_intelligence"
android:icon="@mipmap/ic_launcher"
diff --git a/OWNERS b/OWNERS
index 417ec58..620981f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,8 @@
## People who can approve changes
# Android auto
-rlagos@google.com
+rlagos@google.com # OWNER for SUW related code
+ericberglund@google.com # OWNER for Car Settings related code
# TV Settings
leifhendrik@google.com
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 4e8ae4e..c0453b1 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -21,5 +21,8 @@
<style name="Theme.Settings.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:windowSoftInputMode">adjustPan</item>
</style>
+
+ <style name="Theme.CarSettings" parent="@style/Theme.CarUi.WithToolbar"/>
</resources> \ No newline at end of file
diff --git a/res/xml/car_search_fragment.xml b/res/xml/car_search_fragment.xml
new file mode 100644
index 0000000..201c60a
--- /dev/null
+++ b/res/xml/car_search_fragment.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/app_name_settings_intelligence"/>
diff --git a/src/com/android/settings/intelligence/search/SearchActivity.java b/src/com/android/settings/intelligence/search/SearchActivity.java
index 3521ef3..229c926 100644
--- a/src/com/android/settings/intelligence/search/SearchActivity.java
+++ b/src/com/android/settings/intelligence/search/SearchActivity.java
@@ -17,28 +17,36 @@
package com.android.settings.intelligence.search;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.WindowManager;
import com.android.settings.intelligence.R;
+import com.android.settings.intelligence.search.car.CarSearchFragment;
-public class SearchActivity extends Activity {
+public class SearchActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
+ if (isAutomotive()) {
+ // Automotive relies on a different theme. Apply before calling super so that
+ // fragments are restored properly on configuration changes.
+ setTheme(R.style.Theme_CarSettings);
+ }
super.onCreate(savedInstanceState);
setContentView(R.layout.search_main);
- // Keeps layouts in-place when keyboard opens.
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
- FragmentManager fragmentManager = getFragmentManager();
+ FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
if (fragment == null) {
+ fragment = isAutomotive() ?
+ new CarSearchFragment() : new SearchFragment();
fragmentManager.beginTransaction()
- .add(R.id.main_content, new SearchFragment())
+ .add(R.id.main_content, fragment)
.commit();
}
}
@@ -48,4 +56,8 @@ public class SearchActivity extends Activity {
finish();
return true;
}
+
+ private boolean isAutomotive() {
+ return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
}
diff --git a/src/com/android/settings/intelligence/search/SearchCommon.java b/src/com/android/settings/intelligence/search/SearchCommon.java
new file mode 100644
index 0000000..4666837
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/SearchCommon.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search;
+
+/**
+ * Shared constant variables and identifiers.
+ */
+public class SearchCommon {
+ /**
+ * Instance state key for the currently entered query.
+ */
+ public static final String STATE_QUERY = "state_query";
+ /**
+ * Instance state key for whether or not saved queries are being shown.
+ */
+ public static final String STATE_SHOWING_SAVED_QUERY = "state_showing_saved_query";
+ /**
+ * Instance state key for whether or not a query has been entered yet.
+ */
+ public static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
+
+ /**
+ * Identifier constants for the search loaders.
+ */
+ public static final class SearchLoaderId {
+ /**
+ * Loader identifier to get search results.
+ */
+ public static final int SEARCH_RESULT = 1;
+ /**
+ * Loader identifier to save an entered query.
+ */
+ public static final int SAVE_QUERY_TASK = 2;
+ /**
+ * Loader identifier to remove an entered query.
+ */
+ public static final int REMOVE_QUERY_TASK = 3;
+ /**
+ * Loader identifier to get currently saved queries.
+ */
+ public static final int SAVED_QUERIES = 4;
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/SearchFragment.java b/src/com/android/settings/intelligence/search/SearchFragment.java
index 1ace8fc..c76eda3 100644
--- a/src/com/android/settings/intelligence/search/SearchFragment.java
+++ b/src/com/android/settings/intelligence/search/SearchFragment.java
@@ -20,14 +20,14 @@ package com.android.settings.intelligence.search;
import static com.android.settings.intelligence.nano.SettingsIntelligenceLogProto.SettingsIntelligenceEvent;
import android.app.Activity;
-import android.app.Fragment;
-import android.app.LoaderManager;
import android.content.Context;
-import android.content.Loader;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
+import androidx.loader.content.Loader;
+import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.fragment.app.Fragment;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -64,21 +64,6 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
private static final String TAG = "SearchFragment";
- // State values
- private static final String STATE_QUERY = "state_query";
- private static final String STATE_SHOWING_SAVED_QUERY = "state_showing_saved_query";
- private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
-
- public static final class SearchLoaderId {
- // Search Query IDs
- public static final int SEARCH_RESULT = 1;
-
- // Saved Query IDs
- public static final int SAVE_QUERY_TASK = 2;
- public static final int REMOVE_QUERY_TASK = 3;
- public static final int SAVED_QUERIES = 4;
- }
-
@VisibleForTesting
String mQuery;
@@ -134,9 +119,9 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
mSearchFeatureProvider.initFeedbackButton();
if (savedInstanceState != null) {
- mQuery = savedInstanceState.getString(STATE_QUERY);
- mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
- mShowingSavedQuery = savedInstanceState.getBoolean(STATE_SHOWING_SAVED_QUERY);
+ mQuery = savedInstanceState.getString(SearchCommon.STATE_QUERY);
+ mNeverEnteredQuery = savedInstanceState.getBoolean(SearchCommon.STATE_NEVER_ENTERED_QUERY);
+ mShowingSavedQuery = savedInstanceState.getBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY);
} else {
mShowingSavedQuery = true;
}
@@ -208,9 +193,9 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString(STATE_QUERY, mQuery);
- outState.putBoolean(STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery);
- outState.putBoolean(STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
+ outState.putString(SearchCommon.STATE_QUERY, mQuery);
+ outState.putBoolean(SearchCommon.STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery);
+ outState.putBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
}
@Override
@@ -238,7 +223,7 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
if (isEmptyQuery) {
final LoaderManager loaderManager = getLoaderManager();
- loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
+ loaderManager.destroyLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT);
mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton(getView());
@@ -263,7 +248,7 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
final Activity activity = getActivity();
switch (id) {
- case SearchLoaderId.SEARCH_RESULT:
+ case SearchCommon.SearchLoaderId.SEARCH_RESULT:
return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
default:
return null;
@@ -292,7 +277,7 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
mSavedQueryController.loadSavedQueries();
} else {
final LoaderManager loaderManager = getLoaderManager();
- loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
+ loaderManager.initLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT, null /* args */,
this /* callback */);
}
@@ -338,8 +323,8 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
private void restartLoaders() {
mShowingSavedQuery = false;
final LoaderManager loaderManager = getLoaderManager();
- loaderManager.restartLoader(
- SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
+ loaderManager.restartLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
+ null /* args */, this /* callback */);
}
public String getQuery() {
diff --git a/src/com/android/settings/intelligence/search/car/CarFeatureFactoryImpl.java b/src/com/android/settings/intelligence/search/car/CarFeatureFactoryImpl.java
new file mode 100644
index 0000000..452ab27
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarFeatureFactoryImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import androidx.annotation.Keep;
+
+import com.android.settings.intelligence.overlay.FeatureFactoryImpl;
+import com.android.settings.intelligence.search.SearchFeatureProvider;
+
+/**
+ * FeatureFactory implementation for car settings search.
+ */
+@Keep
+public class CarFeatureFactoryImpl extends FeatureFactoryImpl {
+ @Override
+ public SearchFeatureProvider searchFeatureProvider() {
+ if (mSearchFeatureProvider == null) {
+ mSearchFeatureProvider = new CarSearchFeatureProviderImpl();
+ }
+ return mSearchFeatureProvider;
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/car/CarIntentSearchViewHolder.java b/src/com/android/settings/intelligence/search/car/CarIntentSearchViewHolder.java
new file mode 100644
index 0000000..bc3ef60
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarIntentSearchViewHolder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.android.settings.intelligence.R;
+import com.android.settings.intelligence.search.AppSearchResult;
+import com.android.settings.intelligence.search.SearchResult;
+
+import java.util.List;
+
+/**
+ * ViewHolder for intent based search results.
+ */
+public class CarIntentSearchViewHolder extends CarSearchViewHolder {
+ private static final String TAG = "CarIntentSearchViewHolder";
+ private static final int REQUEST_CODE_NO_OP = 0;
+
+ public CarIntentSearchViewHolder(View view) {
+ super(view);
+ }
+
+ @Override
+ public void onBind(CarSearchFragment fragment, SearchResult result) {
+ mTitle.setText(result.title);
+ if (result instanceof AppSearchResult) {
+ AppSearchResult appResult = (AppSearchResult) result;
+ PackageManager pm = fragment.getActivity().getPackageManager();
+ mIcon.setImageDrawable(appResult.info.loadIcon(pm));
+ } else {
+ mIcon.setImageDrawable(result.icon);
+ }
+ bindBreadcrumbView(result);
+
+ itemView.setOnClickListener(v -> {
+ fragment.onSearchResultClicked(/* resultViewHolder= */ this, result);
+ Intent intent = result.payload.getIntent();
+ if (result instanceof AppSearchResult) {
+ AppSearchResult appResult = (AppSearchResult) result;
+ fragment.getActivity().startActivity(intent);
+ } else {
+ PackageManager pm = fragment.getActivity().getPackageManager();
+ List<ResolveInfo> info = pm.queryIntentActivities(intent, /* flags= */ 0);
+ if (info != null && !info.isEmpty()) {
+ fragment.startActivityForResult(intent, REQUEST_CODE_NO_OP);
+ } else {
+ Log.e(TAG, "Cannot launch search result, title: "
+ + result.title + ", " + intent);
+ }
+ }
+ });
+ }
+
+ private void bindBreadcrumbView(SearchResult result) {
+ if (result.breadcrumbs == null || result.breadcrumbs.isEmpty()) {
+ mSummary.setVisibility(View.GONE);
+ return;
+ }
+ String breadcrumb = result.breadcrumbs.get(0);
+ int count = result.breadcrumbs.size();
+ for (int i = 1; i < count; i++) {
+ breadcrumb = mContext.getString(R.string.search_breadcrumb_connector,
+ breadcrumb, result.breadcrumbs.get(i));
+ }
+ if (breadcrumb == null || TextUtils.isEmpty(breadcrumb.trim())) {
+ mSummary.setVisibility(View.GONE);
+ } else {
+ mSummary.setText(breadcrumb);
+ mSummary.setVisibility(View.VISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/car/CarSearchFeatureProviderImpl.java b/src/com/android/settings/intelligence/search/car/CarSearchFeatureProviderImpl.java
new file mode 100644
index 0000000..2491cbd
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarSearchFeatureProviderImpl.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+
+import com.android.settings.intelligence.search.SearchFeatureProvider;
+import com.android.settings.intelligence.search.SearchFragment;
+import com.android.settings.intelligence.search.SearchResult;
+import com.android.settings.intelligence.search.SearchResultLoader;
+import com.android.settings.intelligence.search.indexing.DatabaseIndexingManager;
+import com.android.settings.intelligence.search.indexing.IndexData;
+import com.android.settings.intelligence.search.indexing.IndexingCallback;
+import com.android.settings.intelligence.search.indexing.car.CarDatabaseIndexingManager;
+import com.android.settings.intelligence.search.query.DatabaseResultTask;
+import com.android.settings.intelligence.search.query.InstalledAppResultTask;
+import com.android.settings.intelligence.search.query.SearchQueryTask;
+import com.android.settings.intelligence.search.savedqueries.SavedQueryLoader;
+import com.android.settings.intelligence.search.sitemap.SiteMapManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+/**
+ * SearchFeatureProvider for car settings search.
+ */
+public class CarSearchFeatureProviderImpl implements SearchFeatureProvider {
+ private static final String TAG = "CarSearchFeatureProvider";
+ private static final long SMART_SEARCH_RANKING_TIMEOUT = 300L;
+
+ private CarDatabaseIndexingManager mDatabaseIndexingManager;
+ private ExecutorService mExecutorService;
+ private SiteMapManager mSiteMapManager;
+
+ @Override
+ public SearchResultLoader getSearchResultLoader(Context context, String query) {
+ return new SearchResultLoader(context, cleanQuery(query));
+ }
+
+ @Override
+ public List<SearchQueryTask> getSearchQueryTasks(Context context, String query) {
+ List<SearchQueryTask> tasks = new ArrayList<>();
+ String cleanQuery = cleanQuery(query);
+ tasks.add(DatabaseResultTask.newTask(context, getSiteMapManager(), cleanQuery));
+ tasks.add(InstalledAppResultTask.newTask(context, getSiteMapManager(), cleanQuery));
+ return tasks;
+ }
+
+ @Override
+ public SavedQueryLoader getSavedQueryLoader(Context context) {
+ return new SavedQueryLoader(context);
+ }
+
+ @Override
+ public DatabaseIndexingManager getIndexingManager(Context context) {
+ if (mDatabaseIndexingManager == null) {
+ mDatabaseIndexingManager = new CarDatabaseIndexingManager(
+ context.getApplicationContext());
+ }
+ return mDatabaseIndexingManager;
+ }
+
+ @Override
+ public SiteMapManager getSiteMapManager() {
+ if (mSiteMapManager == null) {
+ mSiteMapManager = new SiteMapManager();
+ }
+ return mSiteMapManager;
+ }
+
+ @Override
+ public boolean isIndexingComplete(Context context) {
+ return getIndexingManager(context).isIndexingComplete();
+ }
+
+ @Override
+ public void initFeedbackButton() {
+ }
+
+ @Override
+ public void showFeedbackButton(SearchFragment fragment, View root) {
+ }
+
+ @Override
+ public void hideFeedbackButton(View root) {
+ }
+
+ @Override
+ public void searchResultClicked(Context context, String query, SearchResult searchResult) {
+ }
+
+ @Override
+ public boolean isSmartSearchRankingEnabled(Context context) {
+ return false;
+ }
+
+ @Override
+ public long smartSearchRankingTimeoutMs(Context context) {
+ return SMART_SEARCH_RANKING_TIMEOUT;
+ }
+
+ @Override
+ public void searchRankingWarmup(Context context) {
+ }
+
+ @Override
+ public FutureTask<List<Pair<String, Float>>> getRankerTask(Context context, String query) {
+ return null;
+ }
+
+ @Override
+ public void updateIndexAsync(Context context, IndexingCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "updating index async");
+ }
+ getIndexingManager(context).indexDatabase(callback);
+ }
+
+ @Override
+ public ExecutorService getExecutorService() {
+ if (mExecutorService == null) {
+ mExecutorService = Executors.newCachedThreadPool();
+ }
+ return mExecutorService;
+ }
+
+ /**
+ * A generic method to make the query suitable for searching the database.
+ *
+ * @return the cleaned query string
+ */
+ private String cleanQuery(String query) {
+ if (TextUtils.isEmpty(query)) {
+ return null;
+ }
+ if (Locale.getDefault().equals(Locale.JAPAN)) {
+ query = IndexData.normalizeJapaneseString(query);
+ }
+ return query.trim();
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/car/CarSearchFragment.java b/src/com/android/settings/intelligence/search/car/CarSearchFragment.java
new file mode 100644
index 0000000..0c49b11
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarSearchFragment.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import static com.android.car.ui.core.CarUi.requireInsets;
+import static com.android.car.ui.core.CarUi.requireToolbar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.NonNull;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.preference.PreferenceFragment;
+import com.android.car.ui.toolbar.MenuItem;
+import com.android.car.ui.toolbar.Toolbar;
+import com.android.car.ui.toolbar.ToolbarController;
+import com.android.settings.intelligence.R;
+import com.android.settings.intelligence.overlay.FeatureFactory;
+import com.android.settings.intelligence.search.SearchCommon;
+import com.android.settings.intelligence.search.SearchFeatureProvider;
+import com.android.settings.intelligence.search.SearchResult;
+import com.android.settings.intelligence.search.indexing.IndexingCallback;
+import com.android.settings.intelligence.search.savedqueries.car.CarSavedQueryController;
+import com.android.settings.intelligence.search.savedqueries.car.CarSavedQueryViewHolder;
+
+import java.util.List;
+
+/**
+ * Search fragment for car settings.
+ */
+public class CarSearchFragment extends PreferenceFragment implements
+ LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
+
+ private SearchFeatureProvider mSearchFeatureProvider;
+
+ private ToolbarController mToolbar;
+ private RecyclerView mRecyclerView;
+
+ private String mQuery;
+ private boolean mShowingSavedQuery;
+
+ private CarSearchResultsAdapter mSearchAdapter;
+ private CarSavedQueryController mSavedQueryController;
+
+ private final RecyclerView.OnScrollListener mScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ if (dy != 0) {
+ hideKeyboard();
+ }
+ }
+ };
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.car_search_fragment, rootKey);
+ }
+
+ protected ToolbarController getToolbar() {
+ return requireToolbar(requireActivity());
+ }
+
+ protected List<MenuItem> getToolbarMenuItems() {
+ return null;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mSearchFeatureProvider = FeatureFactory.get(context).searchFeatureProvider();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mQuery = savedInstanceState.getString(SearchCommon.STATE_QUERY);
+ mShowingSavedQuery = savedInstanceState.getBoolean(
+ SearchCommon.STATE_SHOWING_SAVED_QUERY);
+ } else {
+ mShowingSavedQuery = true;
+ }
+
+ LoaderManager loaderManager = getLoaderManager();
+ mSearchAdapter = new CarSearchResultsAdapter(/* fragment= */ this);
+ mSavedQueryController = new CarSavedQueryController(
+ getContext(), loaderManager, mSearchAdapter);
+ mSearchFeatureProvider.updateIndexAsync(getContext(), /* indexingCallback= */ this);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mToolbar = getToolbar();
+ if (mToolbar != null) {
+ List<MenuItem> items = getToolbarMenuItems();
+ mToolbar.setTitle(getPreferenceScreen().getTitle());
+ mToolbar.setMenuItems(items);
+ mToolbar.setNavButtonMode(Toolbar.NavButtonMode.BACK);
+ mToolbar.setState(Toolbar.State.SUBPAGE);
+ mToolbar.setState(Toolbar.State.SEARCH);
+ mToolbar.setSearchHint(R.string.abc_search_hint);
+ mToolbar.registerOnSearchListener(this::onQueryTextChange);
+ mToolbar.registerOnSearchCompletedListener(this::onSearchComplete);
+ mToolbar.setShowMenuItemsWhileSearching(true);
+ mToolbar.setSearchQuery(mQuery);
+ }
+ mRecyclerView = getListView();
+ if (mRecyclerView != null) {
+ mRecyclerView.setAdapter(mSearchAdapter);
+ mRecyclerView.addOnScrollListener(mScrollListener);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ onCarUiInsetsChanged(requireInsets(requireActivity()));
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(SearchCommon.STATE_QUERY, mQuery);
+ outState.putBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
+ }
+
+ private void onQueryTextChange(String query) {
+ if (TextUtils.equals(query, mQuery)) {
+ return;
+ }
+ boolean isEmptyQuery = TextUtils.isEmpty(query);
+
+ mQuery = query;
+
+ // If indexing is not finished, register the query text, but don't search.
+ if (!mSearchFeatureProvider.isIndexingComplete(getActivity())) {
+ mToolbar.getProgressBar().setVisible(!isEmptyQuery);
+ return;
+ }
+
+ if (isEmptyQuery) {
+ LoaderManager loaderManager = getLoaderManager();
+ loaderManager.destroyLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT);
+ mShowingSavedQuery = true;
+ mSavedQueryController.loadSavedQueries();
+ } else {
+ restartLoaders();
+ }
+ }
+
+ private void onSearchComplete() {
+ if (!TextUtils.isEmpty(mQuery)) {
+ mSavedQueryController.saveQuery(mQuery);
+ }
+ }
+
+ /**
+ * Gets called when a saved query is clicked.
+ */
+ public void onSavedQueryClicked(CarSavedQueryViewHolder vh, CharSequence query) {
+ String queryString = query.toString();
+ mToolbar.setSearchQuery(queryString);
+ onQueryTextChange(queryString);
+ }
+
+ /**
+ * Gets called when a search result is clicked.
+ */
+ public void onSearchResultClicked(CarSearchViewHolder resultViewHolder, SearchResult result) {
+ mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
+ mSavedQueryController.saveQuery(mQuery);
+ }
+
+ @Override
+ public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
+ Activity activity = getActivity();
+
+ if (id == SearchCommon.SearchLoaderId.SEARCH_RESULT) {
+ return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
+ List<? extends SearchResult> data) {
+ mSearchAdapter.postSearchResults(data);
+ mRecyclerView.scrollToPosition(0);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
+ }
+
+ /**
+ * Gets called when Indexing is completed.
+ */
+ @Override
+ public void onIndexingFinished() {
+ if (getActivity() == null) {
+ return;
+ }
+ mToolbar.getProgressBar().setVisible(false);
+ if (mShowingSavedQuery) {
+ mSavedQueryController.loadSavedQueries();
+ } else {
+ LoaderManager loaderManager = getLoaderManager();
+ loaderManager.initLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
+ /* args= */ null, /* callback= */ this);
+ }
+ requery();
+ }
+
+ private void requery() {
+ if (TextUtils.isEmpty(mQuery)) {
+ return;
+ }
+ String query = mQuery;
+ mQuery = "";
+ onQueryTextChange(query);
+ }
+
+ private void restartLoaders() {
+ mShowingSavedQuery = false;
+ LoaderManager loaderManager = getLoaderManager();
+ loaderManager.restartLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
+ /* args= */ null, /* callback= */ this);
+ }
+
+ private void hideKeyboard() {
+ Activity activity = getActivity();
+ if (activity != null) {
+ View view = activity.getCurrentFocus();
+ InputMethodManager imm = (InputMethodManager)
+ activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm.isActive(view)) {
+ imm.hideSoftInputFromWindow(view.getWindowToken(), /* flags= */ 0);
+ }
+ }
+
+ if (mRecyclerView != null) {
+ mRecyclerView.requestFocus();
+ }
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/car/CarSearchResultsAdapter.java b/src/com/android/settings/intelligence/search/car/CarSearchResultsAdapter.java
new file mode 100644
index 0000000..be35482
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarSearchResultsAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.intelligence.R;
+import com.android.settings.intelligence.search.ResultPayload;
+import com.android.settings.intelligence.search.SearchResult;
+import com.android.settings.intelligence.search.SearchResultDiffCallback;
+import com.android.settings.intelligence.search.savedqueries.car.CarSavedQueryViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RecyclerView Adapter for the car search results RecyclerView.
+ * The adapter uses the CarSearchViewHolder for its view contents.
+ */
+public class CarSearchResultsAdapter extends RecyclerView.Adapter<CarSearchViewHolder> {
+
+ private final CarSearchFragment mFragment;
+ private final List<SearchResult> mSearchResults;
+
+ public CarSearchResultsAdapter(CarSearchFragment fragment) {
+ mFragment = fragment;
+ mSearchResults = new ArrayList<>();
+
+ setHasStableIds(true);
+ }
+
+ @Override
+ public CarSearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ Context context = parent.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ View view;
+ switch (viewType) {
+ case ResultPayload.PayloadType.INTENT:
+ view = inflater.inflate(R.layout.car_ui_preference, parent,
+ /* attachToRoot= */ false);
+ return new CarIntentSearchViewHolder(view);
+ case ResultPayload.PayloadType.SAVED_QUERY:
+ view = inflater.inflate(R.layout.car_ui_preference, parent,
+ /* attachToRoot= */ false);
+ return new CarSavedQueryViewHolder(view);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(CarSearchViewHolder holder, int position) {
+ holder.onBind(mFragment, mSearchResults.get(position));
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mSearchResults.get(position).hashCode();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mSearchResults.get(position).viewType;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSearchResults.size();
+ }
+
+ protected void postSearchResults(List<? extends SearchResult> newSearchResults) {
+ DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
+ new SearchResultDiffCallback(mSearchResults, newSearchResults));
+ mSearchResults.clear();
+ mSearchResults.addAll(newSearchResults);
+ diffResult.dispatchUpdatesTo(/* adapter= */ this);
+ }
+
+ /**
+ * Displays recent searched queries.
+ */
+ public void displaySavedQuery(List<? extends SearchResult> data) {
+ clearResults();
+ mSearchResults.addAll(data);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Clear current search results.
+ */
+ public void clearResults() {
+ mSearchResults.clear();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Get current search results.
+ */
+ public List<SearchResult> getSearchResults() {
+ return mSearchResults;
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/car/CarSearchViewHolder.java b/src/com/android/settings/intelligence/search/car/CarSearchViewHolder.java
new file mode 100644
index 0000000..2657d9a
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/car/CarSearchViewHolder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.car;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.intelligence.search.SearchResult;
+
+/** The ViewHolder for the car search RecyclerView.
+ * There are multiple search result types with different UI requirements, such as Intent results
+ * and saved query results.
+ */
+public abstract class CarSearchViewHolder extends RecyclerView.ViewHolder {
+ protected Context mContext;
+ protected ImageView mIcon;
+ protected TextView mTitle;
+ protected TextView mSummary;
+
+ public CarSearchViewHolder(View view) {
+ super(view);
+ mContext = view.getContext();
+ mIcon = view.findViewById(android.R.id.icon);
+ mTitle = view.findViewById(android.R.id.title);
+ mSummary = view.findViewById(android.R.id.summary);
+ }
+
+ /**
+ * Update the ViewHolder data when bound.
+ */
+ public abstract void onBind(CarSearchFragment fragment, SearchResult result);
+}
diff --git a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java
index 0053f14..32add54 100644
--- a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java
+++ b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java
@@ -178,7 +178,7 @@ public class DatabaseIndexingManager {
private List<IndexData> getIndexData(PreIndexData data) {
if (mConverter == null) {
- mConverter = new IndexDataConverter(mContext);
+ mConverter = getIndexDataConverter(mContext);
}
return mConverter.convertPreIndexDataToIndexData(data);
}
@@ -186,7 +186,7 @@ public class DatabaseIndexingManager {
private List<SiteMapPair> getSiteMapPairs(List<IndexData> indexData,
List<Pair<String, String>> siteMapClassNames) {
if (mConverter == null) {
- mConverter = new IndexDataConverter(mContext);
+ mConverter = getIndexDataConverter(mContext);
}
return mConverter.convertSiteMapPairs(indexData, siteMapClassNames);
}
@@ -312,6 +312,14 @@ public class DatabaseIndexingManager {
}
}
+ /**
+ * Protected method to get a new IndexDataConverter instance. This method can be overridden
+ * in subclasses to substitute in a custom IndexDataConverter.
+ */
+ protected IndexDataConverter getIndexDataConverter(Context context) {
+ return new IndexDataConverter(context);
+ }
+
public class IndexingTask extends AsyncTask<Void, Void, Void> {
@VisibleForTesting
diff --git a/src/com/android/settings/intelligence/search/indexing/IndexData.java b/src/com/android/settings/intelligence/search/indexing/IndexData.java
index 904deaf..7318eaa 100644
--- a/src/com/android/settings/intelligence/search/indexing/IndexData.java
+++ b/src/com/android/settings/intelligence/search/indexing/IndexData.java
@@ -68,7 +68,7 @@ public class IndexData {
private static final Pattern REMOVE_DIACRITICALS_PATTERN
= Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
- private IndexData(Builder builder) {
+ protected IndexData(Builder builder) {
locale = Locale.getDefault().toString();
updatedTitle = normalizeHyphen(builder.mTitle);
updatedSummaryOn = normalizeHyphen(builder.mSummaryOn);
@@ -186,6 +186,18 @@ public class IndexData {
return mKey;
}
+ public String getIntentAction() {
+ return mIntentAction;
+ }
+
+ public String getIntentTargetPackage() {
+ return mIntentTargetPackage;
+ }
+
+ public String getIntentTargetClass() {
+ return mIntentTargetClass;
+ }
+
public Builder setSummaryOn(String summaryOn) {
mSummaryOn = summaryOn;
return this;
@@ -290,9 +302,10 @@ public class IndexData {
}
/**
- * Adds Intent payload to builder.
+ * Builds Intent payload for the builder.
+ * This protected method that can be overridden in a subclass for custom intents.
*/
- private Intent buildIntent(Context context) {
+ protected Intent buildIntent(Context context) {
final Intent intent;
// TODO REFACTOR (b/62807132) With inline results re-add proper intent support
diff --git a/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java b/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java
index bd53596..f57ce6b 100644
--- a/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java
+++ b/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java
@@ -165,7 +165,7 @@ public class IndexDataConverter {
// A row is enabled if it does not show up as an nonIndexableKey
boolean enabled = !(nonIndexableKeys != null && nonIndexableKeys.contains(raw.key));
- final IndexData.Builder builder = new IndexData.Builder();
+ final IndexData.Builder builder = getIndexDataBuilder();
builder.setTitle(raw.title)
.setSummaryOn(raw.summaryOn)
.setEntries(raw.entries)
@@ -244,7 +244,7 @@ public class IndexDataConverter {
headerKeywords = XmlParserUtils.getDataKeywords(context, attrs);
enabled = !nonIndexableKeys.contains(headerKey);
// TODO: Set payload type for header results
- IndexData.Builder headerBuilder = new IndexData.Builder();
+ IndexData.Builder headerBuilder = getIndexDataBuilder();
headerBuilder.setTitle(headerTitle)
.setSummaryOn(headerSummary)
.setScreenTitle(screenTitle)
@@ -286,7 +286,7 @@ public class IndexDataConverter {
isHeaderUnique = false;
}
- builder = new IndexData.Builder();
+ builder = getIndexDataBuilder();
builder.setTitle(title)
.setKeywords(keywords)
.setClassName(sir.className)
@@ -365,4 +365,8 @@ public class IndexDataConverter {
final Set<String> result = nonIndexableKeys.get(authority);
return result != null ? result : new ArraySet<>();
}
+
+ protected IndexData.Builder getIndexDataBuilder() {
+ return new IndexData.Builder();
+ }
}
diff --git a/src/com/android/settings/intelligence/search/indexing/car/CarDatabaseIndexingManager.java b/src/com/android/settings/intelligence/search/indexing/car/CarDatabaseIndexingManager.java
new file mode 100644
index 0000000..3026e82
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/indexing/car/CarDatabaseIndexingManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.indexing.car;
+
+import android.content.Context;
+
+import com.android.settings.intelligence.search.indexing.DatabaseIndexingManager;
+import com.android.settings.intelligence.search.indexing.IndexDataConverter;
+import com.android.settings.intelligence.search.indexing.PreIndexData;
+
+/**
+ * Car extension to {@link DatabaseIndexingManager} to use {@link CarIndexDataConverter} for
+ * converting {@link PreIndexData} into {@link CarIndexData}.
+ */
+public class CarDatabaseIndexingManager extends DatabaseIndexingManager {
+
+ public CarDatabaseIndexingManager(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected IndexDataConverter getIndexDataConverter(Context context) {
+ return new CarIndexDataConverter(context);
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/indexing/car/CarIndexData.java b/src/com/android/settings/intelligence/search/indexing/car/CarIndexData.java
new file mode 100644
index 0000000..a57d003
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/indexing/car/CarIndexData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.indexing.car;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.settings.intelligence.search.indexing.DatabaseIndexingUtils;
+import com.android.settings.intelligence.search.indexing.IndexData;
+
+/**
+ * Car data class representing a single row in the Setting Search results database.
+ */
+public class CarIndexData extends IndexData {
+
+ public CarIndexData(IndexData.Builder builder) {
+ super(builder);
+ }
+
+ /**
+ * Builder class for {@link CarIndexData}, extending {@link IndexData.Builder}, which replaces
+ * all intents with direct search intents, since CarSettings doesn't support
+ * SearchResultTrampolineIntents.
+ */
+ public static class Builder extends IndexData.Builder {
+ @Override
+ protected Intent buildIntent(Context context) {
+ return DatabaseIndexingUtils.buildDirectSearchResultIntent(getIntentAction(),
+ getIntentTargetPackage(), getIntentTargetClass(), getKey());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/intelligence/search/indexing/car/CarIndexDataConverter.java b/src/com/android/settings/intelligence/search/indexing/car/CarIndexDataConverter.java
new file mode 100644
index 0000000..11f35cb
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/indexing/car/CarIndexDataConverter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.indexing.car;
+
+import android.content.Context;
+
+import com.android.settings.intelligence.search.indexing.IndexData;
+import com.android.settings.intelligence.search.indexing.IndexDataConverter;
+import com.android.settings.intelligence.search.indexing.PreIndexData;
+
+/**
+ * Car helper class to convert {@link PreIndexData} to {@link CarIndexData}.
+ */
+public class CarIndexDataConverter extends IndexDataConverter {
+
+ public CarIndexDataConverter(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected IndexData.Builder getIndexDataBuilder() {
+ return new CarIndexData.Builder();
+ }
+}
diff --git a/src/com/android/settings/intelligence/search/savedqueries/SavedQueryController.java b/src/com/android/settings/intelligence/search/savedqueries/SavedQueryController.java
index e4c2cc1..95dfde4 100644
--- a/src/com/android/settings/intelligence/search/savedqueries/SavedQueryController.java
+++ b/src/com/android/settings/intelligence/search/savedqueries/SavedQueryController.java
@@ -16,18 +16,18 @@
package com.android.settings.intelligence.search.savedqueries;
-import android.app.LoaderManager;
import android.content.Context;
-import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
import com.android.settings.intelligence.R;
import com.android.settings.intelligence.overlay.FeatureFactory;
+import com.android.settings.intelligence.search.SearchCommon;
import com.android.settings.intelligence.search.SearchFeatureProvider;
-import com.android.settings.intelligence.search.SearchFragment;
import com.android.settings.intelligence.search.SearchResult;
import com.android.settings.intelligence.search.SearchResultsAdapter;
@@ -59,11 +59,11 @@ public class SavedQueryController implements LoaderManager.LoaderCallbacks,
@Override
public Loader onCreateLoader(int id, Bundle args) {
switch (id) {
- case SearchFragment.SearchLoaderId.SAVE_QUERY_TASK:
+ case SearchCommon.SearchLoaderId.SAVE_QUERY_TASK:
return new SavedQueryRecorder(mContext, args.getString(ARG_QUERY));
- case SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK:
+ case SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK:
return new SavedQueryRemover(mContext);
- case SearchFragment.SearchLoaderId.SAVED_QUERIES:
+ case SearchCommon.SearchLoaderId.SAVED_QUERIES:
return mSearchFeatureProvider.getSavedQueryLoader(mContext);
}
return null;
@@ -72,11 +72,11 @@ public class SavedQueryController implements LoaderManager.LoaderCallbacks,
@Override
public void onLoadFinished(Loader loader, Object data) {
switch (loader.getId()) {
- case SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK:
- mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES,
+ case SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK:
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVED_QUERIES,
null /* args */, this /* callback */);
break;
- case SearchFragment.SearchLoaderId.SAVED_QUERIES:
+ case SearchCommon.SearchLoaderId.SAVED_QUERIES:
if (SearchFeatureProvider.DEBUG) {
Log.d(TAG, "Saved queries loaded");
}
@@ -107,7 +107,7 @@ public class SavedQueryController implements LoaderManager.LoaderCallbacks,
public void saveQuery(String query) {
final Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
- mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVE_QUERY_TASK, args,
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVE_QUERY_TASK, args,
this /* callback */);
}
@@ -116,7 +116,7 @@ public class SavedQueryController implements LoaderManager.LoaderCallbacks,
*/
public void removeQueries() {
final Bundle args = new Bundle();
- mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK, args,
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK, args,
this /* callback */);
}
@@ -124,7 +124,7 @@ public class SavedQueryController implements LoaderManager.LoaderCallbacks,
if (SearchFeatureProvider.DEBUG) {
Log.d(TAG, "loading saved queries");
}
- mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES, null /* args */,
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVED_QUERIES, null /* args */,
this /* callback */);
}
}
diff --git a/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryController.java b/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryController.java
new file mode 100644
index 0000000..fa222a0
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.savedqueries.car;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MenuItem;
+
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.settings.intelligence.overlay.FeatureFactory;
+import com.android.settings.intelligence.search.SearchCommon;
+import com.android.settings.intelligence.search.SearchFeatureProvider;
+import com.android.settings.intelligence.search.SearchResult;
+import com.android.settings.intelligence.search.car.CarSearchResultsAdapter;
+import com.android.settings.intelligence.search.savedqueries.SavedQueryRecorder;
+import com.android.settings.intelligence.search.savedqueries.SavedQueryRemover;
+
+import java.util.List;
+
+/**
+ * Helper class for managing saved queries.
+ */
+public class CarSavedQueryController implements LoaderManager.LoaderCallbacks,
+ MenuItem.OnMenuItemClickListener {
+
+ private static final String ARG_QUERY = "remove_query";
+ private static final String TAG = "CarSearchSavedQueryCtrl";
+
+ private static final int MENU_SEARCH_HISTORY = 1000;
+
+ private final Context mContext;
+ private final LoaderManager mLoaderManager;
+ private final SearchFeatureProvider mSearchFeatureProvider;
+ private final CarSearchResultsAdapter mResultAdapter;
+
+ public CarSavedQueryController(Context context, LoaderManager loaderManager,
+ CarSearchResultsAdapter resultsAdapter) {
+ mContext = context;
+ mLoaderManager = loaderManager;
+ mResultAdapter = resultsAdapter;
+ mSearchFeatureProvider = FeatureFactory.get(context)
+ .searchFeatureProvider();
+ }
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case SearchCommon.SearchLoaderId.SAVE_QUERY_TASK:
+ return new SavedQueryRecorder(mContext, args.getString(ARG_QUERY));
+ case SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK:
+ return new SavedQueryRemover(mContext);
+ case SearchCommon.SearchLoaderId.SAVED_QUERIES:
+ return mSearchFeatureProvider.getSavedQueryLoader(mContext);
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Object data) {
+ switch (loader.getId()) {
+ case SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK:
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVED_QUERIES,
+ /* args= */ null, /* callback= */ this);
+ break;
+ case SearchCommon.SearchLoaderId.SAVED_QUERIES:
+ if (SearchFeatureProvider.DEBUG) {
+ Log.d(TAG, "Saved queries loaded");
+ }
+ mResultAdapter.displaySavedQuery((List<SearchResult>) data);
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (item.getItemId() != MENU_SEARCH_HISTORY) {
+ return false;
+ }
+ removeQueries();
+ return true;
+ }
+
+ /**
+ * Save a query to the DB.
+ */
+ public void saveQuery(String query) {
+ Bundle args = new Bundle();
+ args.putString(ARG_QUERY, query);
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVE_QUERY_TASK, args,
+ /* callback= */ this);
+ }
+
+ /**
+ * Remove all saved queries from the DB.
+ */
+ public void removeQueries() {
+ Bundle args = new Bundle();
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.REMOVE_QUERY_TASK, args,
+ /* callback= */ this);
+ }
+
+ /**
+ * Load the saved queries from the DB.
+ */
+ public void loadSavedQueries() {
+ if (SearchFeatureProvider.DEBUG) {
+ Log.d(TAG, "loading saved queries");
+ }
+ mLoaderManager.restartLoader(SearchCommon.SearchLoaderId.SAVED_QUERIES,
+ /* args= */ null, /* callback= */ this);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryViewHolder.java b/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryViewHolder.java
new file mode 100644
index 0000000..0f03c23
--- /dev/null
+++ b/src/com/android/settings/intelligence/search/savedqueries/car/CarSavedQueryViewHolder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.settings.intelligence.search.savedqueries.car;
+
+import android.view.View;
+
+import com.android.settings.intelligence.R;
+import com.android.settings.intelligence.search.car.CarSearchFragment;
+import com.android.settings.intelligence.search.car.CarSearchViewHolder;
+import com.android.settings.intelligence.search.SearchResult;
+
+/**
+ * ViewHolder for saved queries from past searches.
+ */
+public class CarSavedQueryViewHolder extends CarSearchViewHolder {
+
+ public CarSavedQueryViewHolder(View view) {
+ super(view);
+ }
+
+ @Override
+ public void onBind(CarSearchFragment fragment, SearchResult result) {
+ mTitle.setText(result.title);
+ mIcon.setImageResource(R.drawable.ic_restore);
+ mSummary.setVisibility(View.GONE);
+ itemView.setOnClickListener(v -> {
+ fragment.onSavedQueryClicked(CarSavedQueryViewHolder.this, result.title);
+ });
+ }
+}
diff --git a/src/com/android/settings/intelligence/utils/AsyncLoader.java b/src/com/android/settings/intelligence/utils/AsyncLoader.java
index 54b62b6..edf311e 100644
--- a/src/com/android/settings/intelligence/utils/AsyncLoader.java
+++ b/src/com/android/settings/intelligence/utils/AsyncLoader.java
@@ -1,6 +1,6 @@
package com.android.settings.intelligence.utils;
-import android.content.AsyncTaskLoader;
+import androidx.loader.content.AsyncTaskLoader;
import android.content.Context;
/**