blob: e27dfa58a419f44712291e2f426ea7866ee10061 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.suggestions.entity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.BaseSwitches;
import org.chromium.base.CommandLine;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.omnibox.R;
import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
import org.chromium.components.browser_ui.util.ConversionUtils;
import org.chromium.components.image_fetcher.ImageFetcher;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.OmniboxSuggestionType;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** A class that handles model and view creation for the Entity suggestions. */
public class EntitySuggestionProcessor extends BaseSuggestionViewProcessor {
private static final String TAG = "EntitySP";
private final SuggestionHost mSuggestionHost;
private final Map<GURL, List<PropertyModel>> mPendingImageRequests;
private final Supplier<ImageFetcher> mImageFetcherSupplier;
// Threshold for low RAM devices. We won't be showing entity suggestion images
// on devices that have less RAM than this to avoid bloat and reduce user-visible
// slowdown while spinning up an image decompression process.
// We set the threshold to 1.5GB to reduce number of users affected by this restriction.
// TODO(crbug.com/979426): This threshold is set arbitrarily and should be revisited once
// we have more data about relation between system memory and performance.
private static final int LOW_MEMORY_THRESHOLD_KB =
(int) (1.5 * ConversionUtils.KILOBYTES_PER_GIGABYTE);
/**
* @param context An Android context.
* @param suggestionHost A handle to the object using the suggestions.
*/
public EntitySuggestionProcessor(Context context, SuggestionHost suggestionHost,
ActionChipsDelegate actionChipsDelegate, Supplier<ImageFetcher> imageFetcherSupplier) {
super(context, suggestionHost, actionChipsDelegate, null);
mSuggestionHost = suggestionHost;
mPendingImageRequests = new HashMap<>();
mImageFetcherSupplier = imageFetcherSupplier;
}
@Override
public boolean doesProcessSuggestion(AutocompleteMatch suggestion, int position) {
return suggestion.getType() == OmniboxSuggestionType.SEARCH_SUGGEST_ENTITY;
}
@Override
public int getViewTypeId() {
return OmniboxSuggestionUiType.ENTITY_SUGGESTION;
}
@Override
public PropertyModel createModel() {
return new PropertyModel(EntitySuggestionViewProperties.ALL_KEYS);
}
private void fetchEntityImage(AutocompleteMatch suggestion, PropertyModel model) {
ThreadUtils.assertOnUiThread();
final GURL url = suggestion.getImageUrl();
if (url.isEmpty()) return;
// Ensure an image fetcher is available prior to requesting images.
ImageFetcher imageFetcher = mImageFetcherSupplier.get();
if (imageFetcher == null) return;
// Do not make duplicate answer image requests for the same URL (to avoid generating
// duplicate bitmaps for the same image).
if (mPendingImageRequests.containsKey(url)) {
mPendingImageRequests.get(url).add(model);
return;
}
List<PropertyModel> models = new ArrayList<>();
models.add(model);
mPendingImageRequests.put(url, models);
ImageFetcher.Params params = ImageFetcher.Params.create(
url.getSpec(), ImageFetcher.ENTITY_SUGGESTIONS_UMA_CLIENT_NAME);
imageFetcher.fetchImage(
params, (Bitmap bitmap) -> {
ThreadUtils.assertOnUiThread();
final List<PropertyModel> pendingModels = mPendingImageRequests.remove(url);
if (pendingModels == null || bitmap == null) {
return;
}
for (int i = 0; i < pendingModels.size(); i++) {
PropertyModel pendingModel = pendingModels.get(i);
setSuggestionDrawableState(pendingModel,
SuggestionDrawableState.Builder.forBitmap(getContext(), bitmap)
.setUseRoundedCorners(true)
.setLarge(true)
.build());
}
});
}
@VisibleForTesting
public void applyImageDominantColor(String colorSpec, PropertyModel model) {
if (TextUtils.isEmpty(colorSpec)) {
return;
}
int color;
try {
color = Color.parseColor(colorSpec);
} catch (IllegalArgumentException e) {
// The supplied color information could not be parsed.
return;
}
setSuggestionDrawableState(model,
SuggestionDrawableState.Builder.forColor(color)
.setLarge(true)
.setUseRoundedCorners(true)
.build());
}
@Override
public void populateModel(AutocompleteMatch suggestion, PropertyModel model, int position) {
super.populateModel(suggestion, model, position);
setSuggestionDrawableState(model,
SuggestionDrawableState.Builder
.forDrawableRes(getContext(), R.drawable.ic_suggestion_magnifier)
.setAllowTint(true)
.build());
if (SysUtils.amountOfPhysicalMemoryKB() >= LOW_MEMORY_THRESHOLD_KB
|| CommandLine.getInstance().hasSwitch(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)) {
applyImageDominantColor(suggestion.getImageDominantColor(), model);
fetchEntityImage(suggestion, model);
}
model.set(EntitySuggestionViewProperties.SUBJECT_TEXT, suggestion.getDisplayText());
model.set(EntitySuggestionViewProperties.DESCRIPTION_TEXT, suggestion.getDescription());
setTabSwitchOrRefineAction(model, suggestion, position);
}
}