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