10d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez/*
20d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Copyright (C) 2017 The Android Open Source Project
30d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez *
40d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Licensed under the Apache License, Version 2.0 (the "License");
50d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * you may not use this file except in compliance with the License.
60d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * You may obtain a copy of the License at
70d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez *
80d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez *      http://www.apache.org/licenses/LICENSE-2.0
90d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez *
100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Unless required by applicable law or agreed to in writing, software
110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * distributed under the License is distributed on an "AS IS" BASIS,
120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * See the License for the specific language governing permissions and
140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * limitations under the License.
150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */
160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezpackage androidx.car.widget;
180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport static java.lang.annotation.RetentionPolicy.SOURCE;
200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.content.Context;
220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.content.res.TypedArray;
230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.graphics.drawable.Drawable;
24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.DrawableRes;
25ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.IntDef;
26ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull;
27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable;
28ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.VisibleForTesting;
29ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.interpolator.view.animation.FastOutSlowInInterpolator;
300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.ChangeBounds;
310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.Fade;
320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.TransitionManager;
330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.TransitionSet;
340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.util.AttributeSet;
350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.util.SparseArray;
360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.Gravity;
370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.View;
380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.ViewGroup;
390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.FrameLayout;
400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.ImageButton;
410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.LinearLayout;
420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.RelativeLayout;
430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.Space;
440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport java.lang.annotation.Retention;
460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport java.util.Locale;
470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport androidx.car.R;
490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez/**
510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * An actions panel with three distinctive zones:
520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <ul>
530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Main control: located in the bottom center it shows a highlighted icon and a circular
540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * progress bar.
550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Secondary controls: these are displayed at the left and at the right of the main control.
560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Overflow controls: these are displayed at the left and at the right of the secondary controls
570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * (if the space allows) and on the additional space if the panel is expanded.
580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * </ul>
590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */
600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezpublic class ActionBar extends RelativeLayout {
610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private static final String TAG = "ActionBar";
620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // ActionBar container
640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private ViewGroup mActionBarWrapper;
650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Rows container
660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private ViewGroup mRowsContainer;
670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // All slots in this action bar where 0 is the bottom-start corner of the matrix, and
680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // mNumColumns * nNumRows - 1 is the top-end corner
690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private FrameLayout[] mSlots;
700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /** Views to set in particular {@link SlotPosition}s */
710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private final SparseArray<View> mFixedViews = new SparseArray<>();
720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // View to be used for the expand/collapse action
730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private @Nullable View mExpandCollapseView;
740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Default expand/collapse view to use one is not provided.
750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private View mDefaultExpandCollapseView;
760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Number of rows in actual use. This is the number of extra rows that will be displayed when
770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // the action bar is expanded
780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private int mNumExtraRowsInUse;
790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Whether the action bar is expanded or not.
800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private boolean mIsExpanded;
810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Views to accomodate in the slots.
820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private @Nullable View[] mViews;
830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Number of columns of slots to use.
840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private int mNumColumns;
850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Maximum number of rows to use.
860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private int mNumRows;
870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    @Retention(SOURCE)
890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    @IntDef({SLOT_MAIN, SLOT_LEFT, SLOT_RIGHT, SLOT_EXPAND_COLLAPSE})
900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public @interface SlotPosition {}
910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /** Slot used for main actions {@link ActionBar}, usually at the bottom center */
930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public static final int SLOT_MAIN = 0;
940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /** Slot used to host 'move left', 'rewind', 'previous' or similar secondary actions,
950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * usually at the left of the main action on the bottom row */
960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public static final int SLOT_LEFT = 1;
970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /** Slot used to host 'move right', 'fast-forward', 'next' or similar secondary actions,
980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * usually at the right of the main action on the bottom row */
990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public static final int SLOT_RIGHT = 2;
1000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /** Slot reserved for the expand/collapse button */
1010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public static final int SLOT_EXPAND_COLLAPSE = 3;
1020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Minimum number of columns supported
1040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private static final int MIN_COLUMNS = 3;
1050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    // Weight for the spacers used at the start and end of each slots row.
1060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private static final float SPACERS_WEIGHT = 0.5f;
1070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public ActionBar(Context context) {
1090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        super(context);
1100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        init(context, null, 0, 0);
1110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public ActionBar(Context context, AttributeSet attrs) {
1140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        super(context, attrs);
1150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        init(context, attrs, 0, 0);
1160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public ActionBar(Context context, AttributeSet attrs, int defStyleAttrs) {
1190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        super(context, attrs, defStyleAttrs);
1200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        init(context, attrs, defStyleAttrs, 0);
1210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public ActionBar(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
1240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        super(context, attrs, defStyleAttrs, defStyleRes);
1250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        init(context, attrs, defStyleAttrs, defStyleRes);
1260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private void init(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
1290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        inflate(context, R.layout.action_bar, this);
1300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
1320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                defStyleAttrs, defStyleRes);
1330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mNumColumns = Math.max(ta.getInteger(R.styleable.ActionBar_columns, MIN_COLUMNS),
1340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                MIN_COLUMNS);
1350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        ta.recycle();
1360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mActionBarWrapper = findViewById(R.id.action_bar_wrapper);
1380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mRowsContainer = findViewById(R.id.rows_container);
1390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mNumRows = mRowsContainer.getChildCount();
1400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mSlots = new FrameLayout[mNumColumns * mNumRows];
1410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        for (int i = 0; i < mNumRows; i++) {
1430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            // Slots are reserved in reverse order (first slots are in the bottom row)
1440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            ViewGroup mRow = (ViewGroup) mRowsContainer.getChildAt(mNumRows - i - 1);
1450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            // Inflate space on the left
1460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            Space space = new Space(context);
1470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            mRow.addView(space);
1480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            space.setLayoutParams(new LinearLayout.LayoutParams(0,
1490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    ViewGroup.LayoutParams.MATCH_PARENT, SPACERS_WEIGHT));
1500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            // Inflate necessary number of columns
1510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            for (int j = 0; j < mNumColumns; j++) {
1520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                int pos = i * mNumColumns + j;
1530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                mSlots[pos] = (FrameLayout) inflate(context, R.layout.action_bar_slot, null);
1540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                mSlots[pos].setLayoutParams(new LinearLayout.LayoutParams(0,
1550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                        ViewGroup.LayoutParams.MATCH_PARENT, 1f));
1560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                mRow.addView(mSlots[pos]);
1570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            }
1580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            // Inflate space on the right
1590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            space = new Space(context);
1600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            mRow.addView(space);
1610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            space.setLayoutParams(new LinearLayout.LayoutParams(0,
1620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    ViewGroup.LayoutParams.MATCH_PARENT, SPACERS_WEIGHT));
1630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
1640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mDefaultExpandCollapseView = createIconButton(context, R.drawable.ic_overflow);
1660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mDefaultExpandCollapseView.setContentDescription(context.getString(
1670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                R.string.action_bar_expand_collapse_button));
1680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mDefaultExpandCollapseView.setOnClickListener(v -> onExpandCollapse());
1690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /**
1720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * Returns an index in the {@link #mSlots} array, given a well-known slot position.
1730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     */
1740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private int getSlotIndex(@SlotPosition int slotPosition) {
1750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        switch (slotPosition) {
1760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            case SLOT_MAIN:
1770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                return mNumColumns / 2;
1780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            case SLOT_LEFT:
1790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                return mNumColumns < 3 ? -1 : (mNumColumns / 2) - 1;
1800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            case SLOT_RIGHT:
1810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                return mNumColumns < 2 ? -1 : (mNumColumns / 2) + 1;
1820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            case SLOT_EXPAND_COLLAPSE:
1830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                return mNumColumns - 1;
1840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            default:
1850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                throw new IllegalArgumentException("Unknown position: " + slotPosition);
1860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
1870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
1880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
1890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /**
1900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * Sets or clears the view to be displayed at a particular position.
1910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     *
1920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param view view to be displayed, or null to leave the position available.
1930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param slotPosition position to update
1940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     */
1950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public void setView(@Nullable View view, @SlotPosition int slotPosition) {
1960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        if (view != null) {
1970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            mFixedViews.put(slotPosition, view);
1980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        } else {
1990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            mFixedViews.remove(slotPosition);
2000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
2010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        updateViewsLayout();
2020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /**
2050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * Sets the view to use for the expand/collapse action. If not provided, a default
2060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * {@link ImageButton} will be used. The provided {@link View} should be able be able to display
2070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * changes in the "activated" state appropriately.
2080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     *
2090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param view {@link View} to use for the expand/collapse action.
2100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     */
2110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public void setExpandCollapseView(@NonNull View view) {
2120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mExpandCollapseView = view;
2130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mExpandCollapseView.setOnClickListener(v -> onExpandCollapse());
2140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        updateViewsLayout();
2150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private View getExpandCollapseView() {
2180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        return mExpandCollapseView != null ? mExpandCollapseView : mDefaultExpandCollapseView;
2190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private ImageButton createIconButton(Context context, @DrawableRes int iconResId) {
2220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        ImageButton button = (ImageButton) inflate(context, R.layout.action_bar_button, null);
2230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        Drawable icon = context.getDrawable(iconResId);
2240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        button.setImageDrawable(icon);
2250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        return button;
2260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /**
2290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * Sets the views to include in each available slot of the action bar. Slots will be filled from
2300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * start to end (i.e: left to right) and from bottom to top. If more views than available slots
2310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * are provided, all extra views will be ignored.
2320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     *
2330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param views array of views to include in each available slot.
2340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     */
2350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    public void setViews(@Nullable View[] views) {
2360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mViews = views;
2370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        updateViewsLayout();
2380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private void updateViewsLayout() {
2410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        // Prepare an array of positions taken
2420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        int totalSlots = mSlots.length;
2430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        View[] slotViews = new View[totalSlots];
2440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        // Take all known positions
2460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        for (int i = 0; i < mFixedViews.size(); i++) {
2470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            int index = getSlotIndex(mFixedViews.keyAt(i));
2480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            if (index >= 0 && index < slotViews.length) {
2490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                slotViews[index] = mFixedViews.valueAt(i);
2500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            }
2510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
2520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        // Set all views using both the fixed and flexible positions
2540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        int expandCollapseIndex = getSlotIndex(SLOT_EXPAND_COLLAPSE);
2550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        int lastUsedIndex = 0;
2560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        int viewsIndex = 0;
2570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        for (int i = 0; i < totalSlots; i++) {
2580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            View viewToUse = null;
2590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            if (slotViews[i] != null) {
2610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                // If there is a view assigned for this slot, use it.
2620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                viewToUse = slotViews[i];
2630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            } else if (i == expandCollapseIndex && mViews != null
2640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    && viewsIndex < mViews.length - 1) {
2650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                // If this is the expand/collapse slot, use the corresponding view
2660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                viewToUse = getExpandCollapseView();
2670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            } else if (mViews != null && viewsIndex < mViews.length) {
2680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                // Otherwise, if the slot is not reserved, and if we still have views to assign,
2690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                // take one and assign it to this slot.
2700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                viewToUse = mViews[viewsIndex];
2710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                viewsIndex++;
2720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            }
2730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            setView(viewToUse, mSlots[i]);
2740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            if (viewToUse != null) {
2750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                lastUsedIndex = i;
2760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            }
2770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
2780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mNumExtraRowsInUse = lastUsedIndex / mNumColumns;
2800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private void setView(@Nullable View view, FrameLayout container) {
2830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        container.removeAllViews();
2840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        if (view != null) {
2850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            container.addView(view);
2860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            container.setVisibility(VISIBLE);
2870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
2880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
2890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        } else {
2900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            container.setVisibility(INVISIBLE);
2910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
2920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
2930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    private void onExpandCollapse() {
2950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mIsExpanded = !mIsExpanded;
2960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        mSlots[getSlotIndex(SLOT_EXPAND_COLLAPSE)].setActivated(mIsExpanded);
2970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
2980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        int animationDuration = getContext().getResources().getInteger(mIsExpanded
2990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                ? R.integer.car_action_bar_expand_anim_duration
3000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                : R.integer.car_action_bar_collapse_anim_duration);
3010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        TransitionSet set = new TransitionSet()
3020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                .addTransition(new ChangeBounds())
3030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                .addTransition(new Fade())
3040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                .setDuration(animationDuration)
3050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                .setInterpolator(new FastOutSlowInInterpolator());
3060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        TransitionManager.beginDelayedTransition(mActionBarWrapper, set);
3070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        for (int i = 0; i < mNumExtraRowsInUse; i++) {
3080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? View.VISIBLE : View.GONE);
3090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
3100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
3110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez
3120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    /**
3130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * Returns the view assigned to the given row and column, after layout.
3140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     *
3150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param rowIdx row index from 0 being the top row, and {@link #mNumRows{ -1 being the bottom
3160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * row.
3170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     * @param colIdx column index from 0 on start (left), to {@link #mNumColumns} on end (right)
3180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez     */
3190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    @VisibleForTesting
3200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    @Nullable
3210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    View getViewAt(int rowIdx, int colIdx) {
3220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        if (rowIdx < 0 || rowIdx > mRowsContainer.getChildCount()) {
3230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            throw new IllegalArgumentException(String.format((Locale) null,
3240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    "Row index out of range (requested: %d, max: %d)",
3250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    rowIdx, mRowsContainer.getChildCount()));
3260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
3270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        if (colIdx < 0 || colIdx > mNumColumns) {
3280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez            throw new IllegalArgumentException(String.format((Locale) null,
3290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    "Column index out of range (requested: %d, max: %d)",
3300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                    colIdx, mNumColumns));
3310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        }
3320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        FrameLayout slot = (FrameLayout) ((LinearLayout) mRowsContainer.getChildAt(rowIdx))
3330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez                .getChildAt(colIdx + 1);
3340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez        return slot.getChildCount() > 0 ? slot.getChildAt(0) : null;
3350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez    }
3360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez}
337