1 /*
2  * Copyright (C) 2020 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 package com.android.car.ui.core;
17 
18 import static com.android.car.ui.core.BaseLayoutController.getBaseLayoutController;
19 import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
20 
21 import android.annotation.TargetApi;
22 import android.app.Activity;
23 import android.content.Context;
24 import android.os.Build.VERSION_CODES;
25 import android.view.View;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.recyclerview.widget.RecyclerView;
30 
31 import com.android.car.ui.R;
32 import com.android.car.ui.baselayout.Insets;
33 import com.android.car.ui.baselayout.InsetsChangedListener;
34 import com.android.car.ui.pluginsupport.PluginFactorySingleton;
35 import com.android.car.ui.recyclerview.CarUiListItem;
36 import com.android.car.ui.toolbar.ToolbarController;
37 
38 import java.util.List;
39 import java.util.Objects;
40 
41 /**
42  * Public interface for general CarUi static functions.
43  */
44 @TargetApi(MIN_TARGET_API)
45 public class CarUi {
46 
47     // Unfortunately, because some of our clients don't have a car specific build we can't set the
48     // minSdk to 28. so we need to enforce minSdk to 28 in the code.
49     public static final int MIN_TARGET_API = VERSION_CODES.P;
50     public static final int TARGET_API_R = VERSION_CODES.R;
51 
52     /** Prevent instantiating this class */
CarUi()53     private CarUi() {}
54 
55     /**
56      * Gets a CarUi component, such as {@link com.android.car.ui.widget.CarUiTextView}, from the
57      * view hierarchy. The interfaces for these components don't extend View, so you can't
58      * get them through findViewById().
59      *
60      * @param view The parent view. Its descendants will be searched for the component.
61      * @param id The id of the component.
62      * @param <T> The resulting type of the component, such as
63      *            {@link com.android.car.ui.widget.CarUiTextView}
64      * @return The component found, or null.
65      */
66     @Nullable
67     @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
findCarUiComponentById(@ullable View view, int id)68     public static <T> T findCarUiComponentById(@Nullable View view, int id) {
69         if (view == null) {
70             return null;
71         }
72         View componentView = view.findViewById(id);
73         return componentView != null
74                 ? (T) componentView.getTag(R.id.car_ui_component_reference)
75                 : null;
76     }
77 
78     /**
79      * Same as {@link #findCarUiComponentById(View, int)}, but will throw an exception
80      * if the result is null.
81      */
82     @NonNull
83     @SuppressWarnings("TypeParameterUnusedInFormals")
requireCarUiComponentById(View view, int id)84     public static <T> T requireCarUiComponentById(View view, int id) {
85         return Objects.requireNonNull(findCarUiComponentById(view, id));
86     }
87 
88     /**
89      * Gets the {@link ToolbarController} for an activity. Requires that the Activity uses
90      * Theme.CarUi.WithToolbar, or otherwise sets carUiBaseLayout and carUiToolbar to true.
91      *
92      * See also: {@link #requireToolbar(Activity)}
93      */
94     @Nullable
getToolbar(@ullable Activity activity)95     public static ToolbarController getToolbar(@Nullable Activity activity) {
96         BaseLayoutController controller = getBaseLayoutController(activity);
97         if (controller != null) {
98             return controller.getToolbarController();
99         }
100         return null;
101     }
102 
103     /**
104      * Use this method to create an instance of a {@link RecyclerView.Adapter} for a list of {@link
105      * CarUiListItem} objects.
106      */
createListItemAdapter( Context context, List<? extends CarUiListItem> items)107     public static RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter(
108             Context context, List<? extends CarUiListItem> items) {
109         return PluginFactorySingleton.get(context).createListItemAdapter(items);
110     }
111 
112 
113     /**
114      * Gets the {@link ToolbarController} for an activity. Requires that the Activity uses
115      * Theme.CarUi.WithToolbar, or otherwise sets carUiBaseLayout and carUiToolbar to true.
116      *
117      * <p>See also: {@link #getToolbar(Activity)}
118      *
119      * @throws IllegalArgumentException When the CarUi Toolbar cannot be found.
120      */
121     @NonNull
requireToolbar(@onNull Activity activity)122     public static ToolbarController requireToolbar(@NonNull Activity activity) {
123         ToolbarController result = getToolbar(activity);
124         if (result == null) {
125             throw new IllegalArgumentException("Activity " + activity
126                     + " does not have a CarUi Toolbar!"
127                     + " Are you using Theme.CarUi.WithToolbar?");
128         }
129 
130         return result;
131     }
132 
133     /**
134      * Registering a listener to receive the InsetsChanged updates instead of the Activity.
135      */
replaceInsetsChangedListenerWith(Activity activity, InsetsChangedListener listener)136     public static void replaceInsetsChangedListenerWith(Activity activity,
137             InsetsChangedListener listener) {
138         BaseLayoutController controller = getBaseLayoutController(activity);
139         if (controller != null) {
140             controller.replaceInsetsChangedListenerWith(listener);
141         }
142     }
143 
144     /**
145      * Gets the current {@link Insets} of the given {@link Activity}. Only applies to Activities
146      * using the base layout, ie have the theme attribute "carUiBaseLayout" set to true.
147      *
148      * <p>Note that you likely don't want to use this without also using
149      * {@link com.android.car.ui.baselayout.InsetsChangedListener}, as without it the Insets
150      * will automatically be applied to your Activity's content view.
151      */
152     @Nullable
getInsets(@ullable Activity activity)153     public static Insets getInsets(@Nullable Activity activity) {
154         BaseLayoutController controller = getBaseLayoutController(activity);
155         if (controller != null) {
156             return controller.getInsets();
157         }
158         return null;
159     }
160 
161     /**
162      * Gets the current {@link Insets} of the given {@link Activity}. Only applies to Activities
163      * using the base layout, ie have the theme attribute "carUiBaseLayout" set to true.
164      *
165      * <p>Note that you likely don't want to use this without also using
166      * {@link com.android.car.ui.baselayout.InsetsChangedListener}, as without it the Insets
167      * will automatically be applied to your Activity's content view.
168      *
169      * @throws IllegalArgumentException When the activity is not using base layouts.
170      */
171     @NonNull
requireInsets(@onNull Activity activity)172     public static Insets requireInsets(@NonNull Activity activity) {
173         Insets result = getInsets(activity);
174         if (result == null) {
175             throw new IllegalArgumentException("Activity " + activity
176                     + " does not have a base layout!"
177                     + " Are you using Theme.CarUi.WithToolbar or Theme.CarUi.NoToolbar?");
178         }
179 
180         return result;
181     }
182 
183     /**
184      * Most apps should not use this method, but instead rely on CarUi automatically
185      * installing the base layout into their activities. See {@link #requireToolbar(Activity)}.
186      *
187      * This method installs the base layout *around* the provided view. As a result, this view
188      * must have a parent ViewGroup.
189      *
190      * When using this method, you can't use the other activity-based methods.
191      * ({@link #requireToolbar(Activity)}, {@link #requireInsets(Activity)}, ect.)
192      *
193      * @see #installBaseLayoutAround(View, InsetsChangedListener, boolean, boolean)
194      *
195      * @param view The view to wrap inside a base layout.
196      * @param hasToolbar if there should be a toolbar in the base layout.
197      * @return The {@link ToolbarController}, which will be null if hasToolbar is false.
198      */
199     @Nullable
installBaseLayoutAround( View view, InsetsChangedListener insetsChangedListener, boolean hasToolbar)200     public static ToolbarController installBaseLayoutAround(
201             View view,
202             InsetsChangedListener insetsChangedListener,
203             boolean hasToolbar) {
204         return installBaseLayoutAround(view, insetsChangedListener, hasToolbar, true);
205     }
206 
207     /**
208      * Most apps should not use this method, but instead rely on CarUi automatically
209      * installing the base layout into their activities. See {@link #requireToolbar(Activity)}.
210      *
211      * This method installs the base layout *around* the provided view. As a result, this view
212      * must have a parent ViewGroup.
213      *
214      * When using this method, you can't use the other activity-based methods.
215      * ({@link #requireToolbar(Activity)}, {@link #requireInsets(Activity)}, ect.)
216      *
217      * @param view The view to wrap inside a base layout.
218      * @param hasToolbar if there should be a toolbar in the base layout.
219      * @param fullscreen A hint specifying whether this view we're installing around takes up
220      *                   the whole screen or not. Used to know if putting decorations around
221      *                   the edges is appropriate.
222      * @return The {@link ToolbarController}, which will be null if hasToolbar is false.
223      */
224     @Nullable
installBaseLayoutAround( View view, InsetsChangedListener insetsChangedListener, boolean hasToolbar, boolean fullscreen)225     public static ToolbarController installBaseLayoutAround(
226             View view,
227             InsetsChangedListener insetsChangedListener,
228             boolean hasToolbar,
229             boolean fullscreen) {
230         return PluginFactorySingleton.get(view.getContext())
231                 .installBaseLayoutAround(view, insetsChangedListener, hasToolbar, fullscreen);
232     }
233 }
234