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.sharedlibrarysupport; 17 18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.text.SpannableString; 21 import android.util.AttributeSet; 22 import android.view.View; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 import androidx.recyclerview.widget.RecyclerView; 27 28 import com.android.car.ui.CarUiText; 29 import com.android.car.ui.FocusArea; 30 import com.android.car.ui.FocusAreaAdapterV1; 31 import com.android.car.ui.FocusParkingView; 32 import com.android.car.ui.FocusParkingViewAdapterV1; 33 import com.android.car.ui.R; 34 import com.android.car.ui.appstyledview.AppStyledViewController; 35 import com.android.car.ui.appstyledview.AppStyledViewControllerAdapterV1; 36 import com.android.car.ui.appstyledview.AppStyledViewControllerImpl; 37 import com.android.car.ui.baselayout.Insets; 38 import com.android.car.ui.baselayout.InsetsChangedListener; 39 import com.android.car.ui.recyclerview.CarUiContentListItem; 40 import com.android.car.ui.recyclerview.CarUiHeaderListItem; 41 import com.android.car.ui.recyclerview.CarUiLayoutStyle; 42 import com.android.car.ui.recyclerview.CarUiListItem; 43 import com.android.car.ui.recyclerview.CarUiListItemAdapterAdapterV1; 44 import com.android.car.ui.recyclerview.CarUiRecyclerView; 45 import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout; 46 import com.android.car.ui.recyclerview.RecyclerViewAdapterV1; 47 import com.android.car.ui.sharedlibrary.oemapis.InsetsOEMV1; 48 import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1; 49 import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1; 50 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1; 51 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ContentListItemOEMV1; 52 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.HeaderListItemOEMV1; 53 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.LayoutStyleOEMV1; 54 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ListItemOEMV1; 55 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewAttributesOEMV1; 56 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1; 57 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.SpanSizeLookupOEMV1; 58 import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1; 59 import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1; 60 import com.android.car.ui.toolbar.ToolbarController; 61 import com.android.car.ui.toolbar.ToolbarControllerAdapterV1; 62 import com.android.car.ui.utils.CarUiUtils; 63 import com.android.car.ui.widget.CarUiTextView; 64 65 import java.util.List; 66 import java.util.function.Consumer; 67 68 /** 69 * This class is an wrapper around {@link SharedLibraryFactoryOEMV1} that implements {@link 70 * SharedLibraryFactory}, to provide a version-agnostic way of interfacing with the OEM's 71 * SharedLibraryFactory. 72 */ 73 public final class SharedLibraryFactoryAdapterV1 implements SharedLibraryFactory { 74 SharedLibraryFactoryOEMV1 mOem; 75 SharedLibraryFactoryStub mFactoryStub; 76 SharedLibraryFactoryAdapterV1(SharedLibraryFactoryOEMV1 oem, Context sharedLibContext)77 public SharedLibraryFactoryAdapterV1(SharedLibraryFactoryOEMV1 oem, Context sharedLibContext) { 78 mOem = oem; 79 mFactoryStub = new SharedLibraryFactoryStub(sharedLibContext); 80 81 mOem.setRotaryFactories( 82 c -> new FocusParkingViewAdapterV1(new FocusParkingView(c)), 83 c -> new FocusAreaAdapterV1(new FocusArea(c))); 84 } 85 86 @Override 87 @Nullable installBaseLayoutAround( View contentView, InsetsChangedListener insetsChangedListener, boolean toolbarEnabled, boolean fullscreen)88 public ToolbarController installBaseLayoutAround( 89 View contentView, 90 InsetsChangedListener insetsChangedListener, 91 boolean toolbarEnabled, 92 boolean fullscreen) { 93 94 if (!mOem.customizesBaseLayout()) { 95 return mFactoryStub.installBaseLayoutAround(contentView, 96 insetsChangedListener, toolbarEnabled, fullscreen); 97 } 98 99 ToolbarControllerOEMV1 toolbar = mOem.installBaseLayoutAround(contentView, 100 insets -> insetsChangedListener.onCarUiInsetsChanged(adaptInsets(insets)), 101 toolbarEnabled, fullscreen); 102 103 return toolbar != null 104 ? new ToolbarControllerAdapterV1(contentView.getContext(), toolbar) 105 : null; 106 } 107 108 @NonNull 109 @Override createTextView(Context context, AttributeSet attrs)110 public CarUiTextView createTextView(Context context, AttributeSet attrs) { 111 return mFactoryStub.createTextView(context, attrs); 112 } 113 114 115 @Override createAppStyledView(Context activityContext)116 public AppStyledViewController createAppStyledView(Context activityContext) { 117 AppStyledViewControllerOEMV1 appStyledViewControllerOEMV1 = mOem.createAppStyledView(); 118 return appStyledViewControllerOEMV1 == null ? new AppStyledViewControllerImpl( 119 activityContext) : new AppStyledViewControllerAdapterV1( 120 appStyledViewControllerOEMV1); 121 } 122 adaptInsets(InsetsOEMV1 insetsOEM)123 private Insets adaptInsets(InsetsOEMV1 insetsOEM) { 124 return new Insets(insetsOEM.getLeft(), insetsOEM.getTop(), 125 insetsOEM.getRight(), insetsOEM.getBottom()); 126 } 127 128 @Override createRecyclerView(@onNull Context context, @Nullable AttributeSet attrs)129 public CarUiRecyclerView createRecyclerView(@NonNull Context context, 130 @Nullable AttributeSet attrs) { 131 RecyclerViewAttributesOEMV1 oemAttrs = from(context, attrs); 132 RecyclerViewOEMV1 oemRecyclerView = mOem.createRecyclerView(context, oemAttrs); 133 if (oemRecyclerView != null) { 134 RecyclerViewAdapterV1 rv = new RecyclerViewAdapterV1(context, attrs); 135 rv.setRecyclerViewOEMV1(oemRecyclerView); 136 return rv; 137 } else { 138 return mFactoryStub.createRecyclerView(context, attrs); 139 } 140 } 141 142 @Override createListItemAdapter( List<? extends CarUiListItem> items)143 public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter( 144 List<? extends CarUiListItem> items) { 145 List<ListItemOEMV1> oemItems = CarUiUtils.convertList(items, 146 SharedLibraryFactoryAdapterV1::toOemListItem); 147 148 AdapterOEMV1<? extends ViewHolderOEMV1> oemAdapter = mOem.createListItemAdapter(oemItems); 149 return oemAdapter != null ? new CarUiListItemAdapterAdapterV1(oemAdapter) 150 : mFactoryStub.createListItemAdapter(items); 151 } 152 from(Context context, AttributeSet attrs)153 private static RecyclerViewAttributesOEMV1 from(Context context, AttributeSet attrs) { 154 RecyclerViewAttributesOEMV1 oemAttrs = null; 155 if (attrs != null) { 156 TypedArray a = context.obtainStyledAttributes( 157 attrs, 158 R.styleable.CarUiRecyclerView, 159 0, 160 R.style.Widget_CarUi_CarUiRecyclerView); 161 final int carUiRecyclerViewLayout = a.getInt( 162 R.styleable.CarUiRecyclerView_layoutStyle, 163 CarUiRecyclerViewLayout.LINEAR); 164 final int spanCount = a.getInt( 165 R.styleable.CarUiRecyclerView_numOfColumns, /* defValue= */ 1); 166 final boolean rotaryScrollEnabled = a.getBoolean( 167 R.styleable.CarUiRecyclerView_rotaryScrollEnabled, 168 /* defValue=*/ false); 169 final int orientation = a.getInt( 170 R.styleable.CarUiRecyclerView_android_orientation, 171 CarUiLayoutStyle.VERTICAL); 172 final boolean reversed = a.getBoolean( 173 R.styleable.CarUiRecyclerView_reverseLayout, false); 174 final int size = a.getInt(R.styleable.CarUiRecyclerView_carUiSize, 175 CarUiRecyclerView.SIZE_LARGE); 176 a.recycle(); 177 178 final LayoutStyleOEMV1 layoutStyle = new LayoutStyleOEMV1() { 179 @Override 180 public int getSpanCount() { 181 return spanCount; 182 } 183 184 @Override 185 public int getLayoutType() { 186 switch (carUiRecyclerViewLayout) { 187 case CarUiRecyclerViewLayout.GRID: 188 return LayoutStyleOEMV1.LAYOUT_TYPE_GRID; 189 case CarUiRecyclerViewLayout.LINEAR: 190 default: 191 return LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR; 192 } 193 } 194 195 @Override 196 public int getOrientation() { 197 switch (orientation) { 198 case CarUiLayoutStyle.HORIZONTAL: 199 return LayoutStyleOEMV1.ORIENTATION_HORIZONTAL; 200 case CarUiLayoutStyle.VERTICAL: 201 default: 202 return LayoutStyleOEMV1.ORIENTATION_VERTICAL; 203 } 204 } 205 206 @Override 207 public boolean getReverseLayout() { 208 return reversed; 209 } 210 211 @Override 212 public SpanSizeLookupOEMV1 getSpanSizeLookup() { 213 // This can be set via setLayoutStyle API later. 214 return null; 215 } 216 }; 217 218 oemAttrs = new RecyclerViewAttributesOEMV1() { 219 @Override 220 public boolean isRotaryScrollEnabled() { 221 return rotaryScrollEnabled; 222 } 223 224 @Override 225 public int getSize() { 226 switch (size) { 227 case CarUiRecyclerView.SIZE_SMALL: 228 return RecyclerViewAttributesOEMV1.SIZE_SMALL; 229 case CarUiRecyclerView.SIZE_MEDIUM: 230 return RecyclerViewAttributesOEMV1.SIZE_MEDIUM; 231 case CarUiRecyclerView.SIZE_LARGE: 232 default: 233 return RecyclerViewAttributesOEMV1.SIZE_LARGE; 234 } 235 } 236 237 @Override 238 public LayoutStyleOEMV1 getLayoutStyle() { 239 return layoutStyle; 240 } 241 }; 242 } 243 return oemAttrs; 244 } 245 toOemListItem(CarUiListItem item)246 private static ListItemOEMV1 toOemListItem(CarUiListItem item) { 247 if (item instanceof CarUiHeaderListItem) { 248 CarUiHeaderListItem header = (CarUiHeaderListItem) item; 249 return new HeaderListItemOEMV1.Builder(new SpannableString(header.getTitle())) 250 .setBody(new SpannableString(header.getBody())) 251 .build(); 252 } else if (item instanceof CarUiContentListItem) { 253 CarUiContentListItem contentItem = (CarUiContentListItem) item; 254 255 ContentListItemOEMV1.Builder builder = new ContentListItemOEMV1.Builder( 256 toOemListItemAction(contentItem.getAction())); 257 258 if (contentItem.getTitle() != null) { 259 builder.setTitle( 260 new SpannableString(contentItem.getTitle().getPreferredText())); 261 } 262 263 if (contentItem.getBody() != null) { 264 builder.setBody(new SpannableString( 265 CarUiText.combineMultiLine(contentItem.getBody()))); 266 } 267 268 builder.setIcon(contentItem.getIcon(), 269 toOemListItemIconType(contentItem.getPrimaryIconType())); 270 271 if (contentItem.getAction() == CarUiContentListItem.Action.ICON) { 272 Consumer<ContentListItemOEMV1> listener = 273 contentItem.getSupplementalIconOnClickListener() != null 274 ? oemItem -> 275 contentItem.getSupplementalIconOnClickListener().onClick( 276 contentItem) : null; 277 builder.setSupplementalIcon(contentItem.getSupplementalIcon(), listener); 278 } 279 280 if (contentItem.getOnClickListener() != null) { 281 Consumer<ContentListItemOEMV1> listener = 282 contentItem.getOnClickListener() != null 283 ? oemItem -> 284 contentItem.getOnClickListener().onClick(contentItem) : null; 285 builder.setOnItemClickedListener(listener); 286 } 287 288 builder.setOnCheckedChangeListener( 289 oemItem -> contentItem.setChecked(oemItem.isChecked())) 290 .setActionDividerVisible(contentItem.isActionDividerVisible()) 291 .setEnabled(contentItem.isEnabled()) 292 .setChecked(contentItem.isChecked()) 293 .setActivated(contentItem.isActivated()); 294 return builder.build(); 295 } else { 296 throw new IllegalStateException("Unexpected list item type"); 297 } 298 } 299 toOemListItemAction( CarUiContentListItem.Action action)300 private static ContentListItemOEMV1.Action toOemListItemAction( 301 CarUiContentListItem.Action action) { 302 switch (action) { 303 case NONE: 304 return ContentListItemOEMV1.Action.NONE; 305 case SWITCH: 306 return ContentListItemOEMV1.Action.SWITCH; 307 case CHECK_BOX: 308 return ContentListItemOEMV1.Action.CHECK_BOX; 309 case RADIO_BUTTON: 310 return ContentListItemOEMV1.Action.RADIO_BUTTON; 311 case ICON: 312 return ContentListItemOEMV1.Action.ICON; 313 case CHEVRON: 314 return ContentListItemOEMV1.Action.CHEVRON; 315 default: 316 throw new IllegalStateException("Unexpected list item action type"); 317 } 318 } 319 toOemListItemIconType( CarUiContentListItem.IconType iconType)320 private static ContentListItemOEMV1.IconType toOemListItemIconType( 321 CarUiContentListItem.IconType iconType) { 322 switch (iconType) { 323 case CONTENT: 324 return ContentListItemOEMV1.IconType.CONTENT; 325 case STANDARD: 326 return ContentListItemOEMV1.IconType.STANDARD; 327 case AVATAR: 328 return ContentListItemOEMV1.IconType.AVATAR; 329 default: 330 throw new IllegalStateException("Unexpected list item icon type"); 331 } 332 } 333 } 334