chromium/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionView.java

// Copyright 2020 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.carousel;

import android.content.Context;
import android.content.res.Configuration;
import android.view.KeyEvent;

import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import org.chromium.build.annotations.CheckDiscard;
import org.chromium.build.annotations.MockedInTests;
import org.chromium.chrome.browser.omnibox.suggestions.RecyclerViewSelectionController;
import org.chromium.chrome.browser.omnibox.suggestions.base.SpacingRecyclerViewItemDecoration;
import org.chromium.chrome.browser.util.KeyNavigationUtil;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;

/** View for Carousel Suggestions. */
@MockedInTests
public class BaseCarouselSuggestionView extends RecyclerView {
    private RecyclerViewSelectionController mSelectionController;
    private SpacingRecyclerViewItemDecoration mDecoration;

    /**
     * Constructs a new carousel suggestion view.
     *
     * @param context Current context.
     */
    public BaseCarouselSuggestionView(Context context, SimpleRecyclerViewAdapter adapter) {
        super(context);

        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        setFocusable(true);
        setFocusableInTouchMode(true);
        setItemAnimator(null);
        setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));

        mSelectionController = new RecyclerViewSelectionController(getLayoutManager());
        mSelectionController.setCycleThroughNoSelection(true);
        addOnChildAttachStateChangeListener(mSelectionController);

        setAdapter(adapter);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed()) {
            return mSelectionController.selectPreviousItem();
        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
            return mSelectionController.selectNextItem();
        } else if (KeyNavigationUtil.isEnter(event)) {
            var tile = mSelectionController.getSelectedView();
            if (tile != null) return tile.performClick();
        }
        return superOnKeyDown(keyCode, event);
    }

    /**
     * Proxy calls to super.onKeyDown; call exposed for testing purposes. There is no way to detect
     * calls to super using robolectric.
     */
    @CheckDiscard("Should be inlined except for testing")
    @VisibleForTesting
    public boolean superOnKeyDown(int keyCode, KeyEvent event) {
        return super.onKeyDown(keyCode, event);
    }

    void resetSelection() {
        mSelectionController.setSelectedItem(RecyclerView.NO_POSITION);
    }

    @Override
    public void setSelected(boolean isSelected) {
        if (isSelected) {
            mSelectionController.setSelectedItem(0);
        } else {
            resetSelection();
        }
    }

    @Override
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mDecoration.notifyViewSizeChanged(
                getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT,
                getMeasuredWidth(),
                getMeasuredHeight())) {
            invalidateItemDecorations();
        }
    }

    /* package */ void setSelectionControllerForTesting(
            RecyclerViewSelectionController controller) {
        removeOnChildAttachStateChangeListener(mSelectionController);
        mSelectionController = controller;
        addOnChildAttachStateChangeListener(mSelectionController);
    }

    /* package */ void setItemDecoration(SpacingRecyclerViewItemDecoration decoration) {
        if (mDecoration != null) {
            removeItemDecoration(mDecoration);
        }
        mDecoration = decoration;
        if (mDecoration != null) {
            addItemDecoration(mDecoration);
        }
    }
}