1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.car.drawer;
18
19import android.app.Activity;
20import android.car.drivingstate.CarUxRestrictions;
21import android.content.Context;
22import android.graphics.PorterDuff;
23import android.graphics.drawable.Drawable;
24import android.os.Bundle;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28
29import androidx.annotation.Nullable;
30import androidx.car.R;
31import androidx.car.utils.CarUxRestrictionsHelper;
32import androidx.car.widget.PagedListView;
33import androidx.recyclerview.widget.RecyclerView;
34
35/**
36 * Base adapter for displaying items in the car navigation drawer, which uses a
37 * {@link PagedListView}.
38 *
39 * <p>Subclasses can optionally set the title that will be displayed when displaying the contents
40 * of the drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later
41 * on. The title of the root adapter will also be the main title showed in the toolbar when the
42 * drawer is closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more
43 * information.
44 *
45 * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
46 * should implement {@link #getActualItemCount()}.
47 *
48 * <p>To enable support for {@link CarUxRestrictions}, call {@link #start()} in your
49 * {@code Activity}'s {@link android.app.Activity#onCreate(Bundle)}, and {@link #stop()} in
50 * {@link Activity#onStop()}.
51 */
52public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
53        implements PagedListView.ItemCap, DrawerItemClickListener {
54    private final boolean mShowDisabledListOnEmpty;
55    private final Drawable mEmptyListDrawable;
56    private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
57    private CharSequence mTitle;
58    private TitleChangeListener mTitleChangeListener;
59
60    private final CarUxRestrictionsHelper mUxRestrictionsHelper;
61    private CarUxRestrictions mCurrentUxRestrictions;
62
63    /**
64     * Enables support for {@link CarUxRestrictions}.
65     *
66     * <p>This method can be called from {@code Activity}'s {@link Activity#onStart()}, or at the
67     * time of construction.
68     *
69     * <p>This method must be accompanied with a matching {@link #stop()} to avoid leak.
70     */
71    public void start() {
72        mUxRestrictionsHelper.start();
73    }
74
75    /**
76     * Disables support for {@link CarUxRestrictions}, and frees up resources.
77     *
78     * <p>This method should be called from {@code Activity}'s {@link Activity#onStop()}, or at the
79     * time of this adapter being discarded.
80     */
81    public void stop() {
82        mUxRestrictionsHelper.stop();
83    }
84
85    /**
86     * Interface for a class that will be notified a new title has been set on this adapter.
87     */
88    interface TitleChangeListener {
89        /**
90         * Called when {@link #setTitle(CharSequence)} has been called and the title has been
91         * changed.
92         */
93        void onTitleChanged(@Nullable CharSequence newTitle);
94    }
95
96    protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) {
97        mShowDisabledListOnEmpty = showDisabledListOnEmpty;
98
99        mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
100        mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint),
101                PorterDuff.Mode.SRC_IN);
102
103        mUxRestrictionsHelper =
104                new CarUxRestrictionsHelper(context, carUxRestrictions -> {
105                    mCurrentUxRestrictions = carUxRestrictions;
106                    notifyDataSetChanged();
107                });
108    }
109
110    /** Returns the title set via {@link #setTitle(CharSequence)}. */
111    CharSequence getTitle() {
112        return mTitle;
113    }
114
115    /** Updates the title to display in the toolbar for this Adapter. */
116    public final void setTitle(@Nullable CharSequence title) {
117        mTitle = title;
118
119        if (mTitleChangeListener != null) {
120            mTitleChangeListener.onTitleChanged(mTitle);
121        }
122    }
123
124    /** Sets a listener to be notified whenever the title of this adapter has been changed. */
125    void setTitleChangeListener(@Nullable TitleChangeListener listener) {
126        mTitleChangeListener = listener;
127    }
128
129    @Override
130    public final void setMaxItems(int maxItems) {
131        mMaxItems = maxItems;
132    }
133
134    @Override
135    public final int getItemCount() {
136        if (shouldShowDisabledListItem()) {
137            return 1;
138        }
139        return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
140    }
141
142    /**
143     * Returns the absolute number of items that can be displayed in the list.
144     *
145     * <p>A class should implement this method to supply the number of items to be displayed.
146     * Returning 0 from this method will cause an empty list icon to be displayed in the drawer.
147     *
148     * <p>A class should override this method rather than {@link #getItemCount()} because that
149     * method is handling the logic of when to display the empty list icon. It will return 1 when
150     * {@link #getActualItemCount()} returns 0.
151     *
152     * @return The number of items to be displayed in the list.
153     */
154    protected abstract int getActualItemCount();
155
156    @Override
157    public final int getItemViewType(int position) {
158        if (shouldShowDisabledListItem()) {
159            return R.layout.car_drawer_list_item_empty;
160        }
161
162        return usesSmallLayout(position)
163                ? R.layout.car_drawer_list_item_small
164                : R.layout.car_drawer_list_item_normal;
165    }
166
167    /**
168     * Used to indicate the layout used for the Drawer item at given position. Subclasses can
169     * override this to use normal layout which includes text element below title.
170     *
171     * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}.
172     * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used.
173     *
174     * @param position Adapter position of item.
175     * @return Whether the item at this position will use a small layout (default) or normal layout.
176     */
177    protected boolean usesSmallLayout(int position) {
178        return true;
179    }
180
181    @Override
182    public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
183        View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
184        return new DrawerItemViewHolder(view);
185    }
186
187    @Override
188    public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
189        // Car may not be initialized thus current UXR will not be available.
190        if (mCurrentUxRestrictions != null) {
191            holder.complyWithUxRestrictions(mCurrentUxRestrictions);
192        }
193
194        if (shouldShowDisabledListItem()) {
195            holder.getTitle().setText(null);
196            holder.getIcon().setImageDrawable(mEmptyListDrawable);
197            holder.setItemClickListener(null);
198        } else {
199            holder.setItemClickListener(this);
200            populateViewHolder(holder, position);
201        }
202    }
203
204    /**
205     * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it
206     * has been configured to show and there are no items to be displayed.
207     */
208    private boolean shouldShowDisabledListItem() {
209        return mShowDisabledListOnEmpty && getActualItemCount() == 0;
210    }
211
212    /**
213     * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some
214     * element is not used, it should be nulled out since these ViewHolder/View's are recycled.
215     */
216    protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
217
218    /**
219     * Called when this adapter has been popped off the stack and is no longer needed. Subclasses
220     * can override to do any necessary cleanup.
221     */
222    public void cleanup() {}
223}
224