blob: 7b292a9458a3f9bc16e1691887b327f8a398fe85 [file] [log] [blame]
// Copyright 2023 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;
import android.content.Context;
import android.os.Handler;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.chromium.base.TraceEvent;
import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
import java.util.ArrayList;
import java.util.List;
/**
* RecyclerView pool that:
* 1. Pre-creates a hardcoded set of ViewHolders.
* 2. Records the performance of the view recycling mechanism.
*/
public class PreWarmingRecycledViewPool extends RecycledViewPool {
private static final long STEP_MILLIS = 50;
private static class ViewTypeAndCount {
public final int viewType;
public final int count;
public ViewTypeAndCount(int viewType, int count) {
this.viewType = viewType;
assert count > 0;
this.count = count;
}
}
private final ViewTypeAndCount[] mViewsToCreate = new ViewTypeAndCount[] {
new ViewTypeAndCount(OmniboxSuggestionUiType.EDIT_URL_SUGGESTION, 1),
new ViewTypeAndCount(OmniboxSuggestionUiType.TILE_NAVSUGGEST, 1),
new ViewTypeAndCount(OmniboxSuggestionUiType.HEADER, 1),
new ViewTypeAndCount(OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION, 1),
new ViewTypeAndCount(OmniboxSuggestionUiType.DEFAULT, 15),
new ViewTypeAndCount(OmniboxSuggestionUiType.ENTITY_SUGGESTION, 3)};
private OmniboxSuggestionsDropdownAdapter mAdapter;
private final Handler mHandler;
private final FrameLayout mDummyParent;
private boolean mStopCreatingViews;
private final List<ViewHolder> mPrewarmedViews = new ArrayList<>(22);
PreWarmingRecycledViewPool(
OmniboxSuggestionsDropdownAdapter adapter, Context context, Handler handler) {
mAdapter = adapter;
mHandler = handler;
mDummyParent = new FrameLayout(context);
// The list below should include suggestions defined in OmniboxSuggestionUiType
// and specify the maximum anticipated volume of suggestions of each type.
// For readability reasons, keep the order of this list same as the order of
// the types defined in OmniboxSuggestionUiType.
setMaxRecycledViews(OmniboxSuggestionUiType.DEFAULT, 20);
setMaxRecycledViews(OmniboxSuggestionUiType.EDIT_URL_SUGGESTION, 1);
setMaxRecycledViews(OmniboxSuggestionUiType.ANSWER_SUGGESTION, 1);
setMaxRecycledViews(OmniboxSuggestionUiType.ENTITY_SUGGESTION, 8);
setMaxRecycledViews(OmniboxSuggestionUiType.TAIL_SUGGESTION, 15);
setMaxRecycledViews(OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION, 1);
setMaxRecycledViews(OmniboxSuggestionUiType.HEADER, 4);
setMaxRecycledViews(OmniboxSuggestionUiType.TILE_NAVSUGGEST, 1);
setMaxRecycledViews(OmniboxSuggestionUiType.PEDAL_SUGGESTION, 3);
setMaxRecycledViews(OmniboxSuggestionUiType.DIVIDER_LINE, 1);
}
public void destroy() {
stopCreatingViews();
mAdapter = null;
}
public void onNativeInitialized() {
if (OmniboxFeatures.shouldPreWarmRecyclerViewPool()) {
startCreatingViews();
}
}
/**
* Starts creating views. This will immediately post a separate delayed task for every view we
* intend to create with a delay equal to STEP_MILLIS * order_of_view_creation.
* */
public void startCreatingViews() {
if (mStopCreatingViews) return;
for (var viewTypeAndCount : mViewsToCreate) {
for (int index = 0; index < viewTypeAndCount.count; ++index) {
mHandler.postDelayed(() -> {
createViewHolder(viewTypeAndCount.viewType);
}, STEP_MILLIS * (index + 1));
}
}
}
/**
* Stops the task posted by {@link #startCreatingViews()} from creating any more views and
* removes it from the handler. Places any pre-created views into the pool.
*/
@VisibleForTesting
void stopCreatingViews() {
if (mStopCreatingViews) return;
mStopCreatingViews = true;
mHandler.removeCallbacks(null);
putViewsIntoPool();
}
private void createViewHolder(@OmniboxSuggestionUiType int viewType) {
if (mAdapter == null || mStopCreatingViews) return;
try (TraceEvent t = TraceEvent.scoped("PreWarmingRecycledViewPool.createNextViewHolder")) {
mPrewarmedViews.add(mAdapter.createViewHolder(mDummyParent, viewType));
}
}
private void putViewsIntoPool() {
for (var viewHolder : mPrewarmedViews) {
putRecycledView(viewHolder);
}
mPrewarmedViews.clear();
}
@Override
public ViewHolder getRecycledView(int viewType) {
stopCreatingViews();
ViewHolder result = super.getRecycledView(viewType);
if (result == null) {
SuggestionsMetrics.recordSuggestionsViewCreatedType(viewType);
} else {
SuggestionsMetrics.recordSuggestionsViewReusedType(viewType);
}
return result;
}
}