blob: a326eea242dfcc4dd6e6bbfe032770563c137984 [file] [log] [blame]
/*
* 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 static com.android.car.ui.utils.CarUiUtils.drawableToBitmap;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
import com.android.car.ui.preference.PreferenceFragment;
import com.android.car.ui.recyclerview.CarUiContentListItem;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
import com.android.car.ui.toolbar.MenuItem;
import com.android.car.ui.toolbar.NavButtonMode;
import com.android.car.ui.toolbar.SearchConfig;
import com.android.car.ui.toolbar.SearchMode;
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.AppSearchResult;
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 java.util.ArrayList;
import java.util.List;
/**
* Search fragment for car settings.
*/
public class CarSearchFragment extends PreferenceFragment implements
LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
private static final String TAG = "CarSearchFragment";
private static final int REQUEST_CODE_NO_OP = 0;
private SearchFeatureProvider mSearchFeatureProvider;
private ToolbarController mToolbar;
private CarUiRecyclerView mRecyclerView;
private String mQuery;
private boolean mShowingSavedQuery;
private CarSearchResultsAdapter mSearchAdapter;
private CarSavedQueryController mSavedQueryController;
private final CarUiRecyclerView.OnScrollListener mScrollListener =
new CarUiRecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull CarUiRecyclerView recyclerView, int dx, int dy) {
if (dy != 0) {
hideKeyboard();
}
}
@Override
public void onScrollStateChanged(@NonNull CarUiRecyclerView recyclerView,
int newState) {}
};
@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);
mToolbar = getToolbar();
mSavedQueryController = new CarSavedQueryController(
getContext(), loaderManager, mSearchAdapter, mToolbar, this);
mSearchFeatureProvider.updateIndexAsync(getContext(), /* indexingCallback= */ this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mToolbar != null) {
List<MenuItem> items = getToolbarMenuItems();
mToolbar.setTitle(getPreferenceScreen().getTitle());
mToolbar.setMenuItems(items);
mToolbar.setNavButtonMode(NavButtonMode.BACK);
mToolbar.setSearchMode(SearchMode.SEARCH);
mToolbar.setSearchHint(R.string.abc_search_hint);
mToolbar.registerSearchListener(this::onQueryTextChange);
mToolbar.registerSearchCompletedListener(this::onSearchComplete);
mToolbar.setShowMenuItemsWhileSearching(true);
mToolbar.setSearchQuery(mQuery);
}
mRecyclerView = getCarUiRecyclerView();
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(CharSequence query) {
String queryString = query.toString();
mToolbar.setSearchQuery(queryString);
onQueryTextChange(queryString);
hideKeyboard();
}
@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) {
if (mToolbar.getSearchCapabilities().canShowSearchResultItems()) {
List<CarUiImeSearchListItem> searchItems = new ArrayList<>();
for (SearchResult result : data) {
CarUiImeSearchListItem item = new CarUiImeSearchListItem(
CarUiContentListItem.Action.ICON);
item.setTitle(result.title);
if (result.breadcrumbs != null && !result.breadcrumbs.isEmpty()) {
item.setBody(getBreadcrumb(result));
}
if (result instanceof AppSearchResult) {
AppSearchResult appResult = (AppSearchResult) result;
PackageManager pm = getActivity().getPackageManager();
Drawable drawable = appResult.info.loadIcon(pm);
Bitmap bm = drawableToBitmap(drawable);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bm);
item.setIcon(bitmapDrawable);
} else if (result.icon != null) {
Bitmap bm = drawableToBitmap(result.icon);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bm);
item.setIcon(bitmapDrawable);
}
item.setOnItemClickedListener(v -> onSearchResultClicked(result));
searchItems.add(item);
}
mToolbar.setSearchConfig(SearchConfig.builder()
.setSearchResultItems(searchItems)
.build());
}
mSearchAdapter.postSearchResults(data);
mRecyclerView.scrollToPosition(0);
}
private String getBreadcrumb(SearchResult result) {
String breadcrumb = result.breadcrumbs.get(0);
int count = result.breadcrumbs.size();
for (int i = 1; i < count; i++) {
breadcrumb = getContext().getString(R.string.search_breadcrumb_connector,
breadcrumb, result.breadcrumbs.get(i));
}
return breadcrumb;
}
/**
* Gets called when a search result is clicked.
*/
protected void onSearchResultClicked(SearchResult result) {
mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
mSavedQueryController.saveQuery(mQuery);
// Hide keyboard to apply the proper insets before the activity launches.
// TODO (b/187074444): remove if WindowManager updates ordering of insets such that they are
// applied before new activities are launched.
hideKeyboard();
Intent intent = result.payload.getIntent();
if (result instanceof AppSearchResult) {
getActivity().startActivity(intent);
} else {
PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> info = pm.queryIntentActivities(intent, /* flags= */ 0);
if (info != null && !info.isEmpty()) {
startActivityForResult(intent, REQUEST_CODE_NO_OP);
} else {
Log.e(TAG, "Cannot launch search result, title: "
+ result.title + ", " + intent);
}
}
}
@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.getView().hasFocus()) {
mRecyclerView.getView().requestFocus();
}
}
}