diff options
author | Xin Li <delphij@google.com> | 2020-09-08 16:56:09 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-09-08 16:56:09 -0700 |
commit | fedfa990901039d259559cf9889539bfa2192fa9 (patch) | |
tree | 36fab9a862f8f64d327c308229dcc9196f5ebda1 | |
parent | 9c7a808ea96fd072e1fd68b4bcd1733f8ff4a3b2 (diff) | |
parent | a4636f52c31ebd907997b0faef28c1939b29b817 (diff) | |
download | SettingsIntelligence-fedfa990901039d259559cf9889539bfa2192fa9.tar.gz |
Merge Android R
Bug: 168057903
Merged-In: I2c3ca1f0794de3b0079b4715f7ce6d33cd786bdc
Change-Id: Ic98265603b0e54f50c2acfab9e4e4149a090ef69
24 files changed, 1199 insertions, 59 deletions
@@ -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" @@ -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; /** |