1 /* 2 * Copyright (C) 2021 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.chassis.car.ui.plugin.recyclerview; 17 18 import static androidx.recyclerview.widget.RecyclerView.VERTICAL; 19 20 import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_LARGE; 21 import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_MEDIUM; 22 import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_SMALL; 23 24 import android.car.drivingstate.CarUxRestrictions; 25 import android.content.Context; 26 import android.view.InputDevice; 27 import android.view.LayoutInflater; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.widget.FrameLayout; 32 import android.widget.LinearLayout; 33 34 import androidx.annotation.LayoutRes; 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.recyclerview.widget.GridLayoutManager; 38 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; 39 import androidx.recyclerview.widget.LinearLayoutManager; 40 import androidx.recyclerview.widget.OrientationHelper; 41 import androidx.recyclerview.widget.RecyclerView; 42 import androidx.recyclerview.widget.RecyclerView.Adapter; 43 import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener; 44 import androidx.recyclerview.widget.RecyclerView.ViewHolder; 45 46 import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1; 47 import com.android.car.ui.plugin.oemapis.recyclerview.LayoutStyleOEMV1; 48 import com.android.car.ui.plugin.oemapis.recyclerview.OnChildAttachStateChangeListenerOEMV1; 49 import com.android.car.ui.plugin.oemapis.recyclerview.OnScrollListenerOEMV1; 50 import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1; 51 import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1; 52 import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1; 53 54 import com.chassis.car.ui.plugin.R; 55 import com.chassis.car.ui.plugin.recyclerview.AdapterWrapper.ViewHolderWrapper; 56 import com.chassis.car.ui.plugin.uxr.CarUxRestrictionsUtil; 57 58 import java.util.ArrayList; 59 import java.util.List; 60 61 /** 62 * Reference OEM implementation for RecyclerView 63 */ 64 public final class RecyclerViewImpl extends FrameLayout implements RecyclerViewOEMV1 { 65 66 /** 67 * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_CONTAINER} 68 */ 69 private static final String ROTARY_CONTAINER = 70 "com.android.car.ui.utils.ROTARY_CONTAINER"; 71 /** 72 * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_HORIZONTALLY_SCROLLABLE} 73 */ 74 private static final String ROTARY_HORIZONTALLY_SCROLLABLE = 75 "com.android.car.ui.utils.HORIZONTALLY_SCROLLABLE"; 76 /** 77 * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_VERTICALLY_SCROLLABLE} 78 */ 79 private static final String ROTARY_VERTICALLY_SCROLLABLE = 80 "com.android.car.ui.utils.VERTICALLY_SCROLLABLE"; 81 82 @NonNull 83 private final RecyclerView mRecyclerView; 84 85 private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener = 86 new UxRestrictionChangedListener(); 87 @NonNull 88 private final CarUxRestrictionsUtil mCarUxRestrictionsUtil; 89 90 @Nullable 91 private final DefaultScrollBar mScrollBar; 92 93 @NonNull 94 private final List<OnScrollListenerOEMV1> mScrollListeners = new ArrayList<>(); 95 96 @NonNull 97 private final RecyclerView.OnScrollListener mOnScrollListener = 98 new RecyclerView.OnScrollListener() { 99 @Override 100 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 101 for (OnScrollListenerOEMV1 listener : mScrollListeners) { 102 listener.onScrolled(RecyclerViewImpl.this, dx, dy); 103 } 104 } 105 106 @Override 107 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 108 for (OnScrollListenerOEMV1 listener : mScrollListeners) { 109 listener.onScrollStateChanged(RecyclerViewImpl.this, 110 toInternalScrollState(newState)); 111 } 112 } 113 }; 114 115 @NonNull 116 private final List<OnChildAttachStateChangeListenerOEMV1> mOnChildAttachStateChangeListeners = 117 new ArrayList<>(); 118 119 @NonNull 120 private final RecyclerView.OnChildAttachStateChangeListener mOnChildAttachStateChangeListener = 121 new OnChildAttachStateChangeListener() { 122 @Override 123 public void onChildViewAttachedToWindow(@NonNull View view) { 124 for (OnChildAttachStateChangeListenerOEMV1 listener : 125 mOnChildAttachStateChangeListeners) { 126 listener.onChildViewAttachedToWindow(view); 127 } 128 } 129 130 @Override 131 public void onChildViewDetachedFromWindow(@NonNull View view) { 132 for (OnChildAttachStateChangeListenerOEMV1 listener : 133 mOnChildAttachStateChangeListeners) { 134 listener.onChildViewDetachedFromWindow(view); 135 } 136 } 137 }; 138 139 @Nullable 140 private LayoutStyleOEMV1 mLayoutStyle; 141 RecyclerViewImpl(@onNull Context context)142 public RecyclerViewImpl(@NonNull Context context) { 143 this(context, null); 144 } 145 RecyclerViewImpl(@onNull Context context, @Nullable RecyclerViewAttributesOEMV1 attrs)146 public RecyclerViewImpl(@NonNull Context context, 147 @Nullable RecyclerViewAttributesOEMV1 attrs) { 148 super(context); 149 boolean scrollBarEnabled = context.getResources().getBoolean(R.bool.scrollbar_enable); 150 @LayoutRes int layout = R.layout.recycler_view_no_scrollbar; 151 if (scrollBarEnabled) { 152 int size = attrs != null ? attrs.getSize() : SIZE_LARGE; 153 switch (size) { 154 case SIZE_SMALL: 155 layout = R.layout.recycler_view_small; 156 break; 157 case SIZE_MEDIUM: 158 layout = R.layout.recycler_view_medium; 159 break; 160 case SIZE_LARGE: 161 layout = R.layout.recycler_view; 162 } 163 } 164 165 LayoutInflater factory = LayoutInflater.from(context); 166 View rootView = factory.inflate(layout, this, true); 167 mRecyclerView = rootView.requireViewById(R.id.recycler_view); 168 169 // Set to false so the items below the toolbar are visible. 170 mRecyclerView.setClipToPadding(false); 171 172 mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context); 173 174 if (attrs != null) { 175 setLayoutStyle(attrs.getLayoutStyle()); 176 setBackground(attrs.getBackground()); 177 setPadding(attrs.getPaddingLeft(), attrs.getPaddingTop(), attrs.getPaddingRight(), 178 attrs.getPaddingBottom()); 179 setMinimumHeight(attrs.getMinHeight()); 180 setMinimumWidth(attrs.geMinWidth()); 181 182 LayoutParams params = new LayoutParams(attrs.getLayoutWidth(), attrs.getLayoutHeight()); 183 params.setMargins(attrs.getMarginLeft(), attrs.getMarginTop(), attrs.getMarginRight(), 184 attrs.getMarginBottom()); 185 setLayoutParams(params); 186 187 mLayoutStyle = attrs.getLayoutStyle(); 188 } else { 189 mLayoutStyle = new LayoutStyleOEMV1() { 190 @Override 191 public int getSpanCount() { 192 return 1; 193 } 194 195 @Override 196 public int getLayoutType() { 197 return LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR; 198 } 199 200 @Override 201 public int getOrientation() { 202 return LayoutStyleOEMV1.ORIENTATION_VERTICAL; 203 } 204 205 @Override 206 public boolean getReverseLayout() { 207 return false; 208 } 209 }; 210 } 211 212 setLayoutStyle(mLayoutStyle); 213 214 boolean rotaryScrollEnabled = attrs != null && attrs.isRotaryScrollEnabled(); 215 initRotaryScroll(mRecyclerView, rotaryScrollEnabled, getLayoutStyle().getOrientation()); 216 217 if (!scrollBarEnabled) { 218 mScrollBar = null; 219 return; 220 } 221 222 mRecyclerView.setVerticalScrollBarEnabled(false); 223 mRecyclerView.setHorizontalScrollBarEnabled(false); 224 225 mScrollBar = new DefaultScrollBar(); 226 mScrollBar.initialize(context, mRecyclerView, rootView.requireViewById(R.id.scroll_bar)); 227 } 228 229 @Override setAdapter(AdapterOEMV1<V> adapterV1)230 public <V extends ViewHolderOEMV1> void setAdapter(AdapterOEMV1<V> adapterV1) { 231 if (adapterV1 == null) { 232 mRecyclerView.setAdapter(null); 233 } else { 234 mRecyclerView.setAdapter(new AdapterWrapper(adapterV1)); 235 } 236 } 237 238 @Override addOnScrollListener(OnScrollListenerOEMV1 listener)239 public void addOnScrollListener(OnScrollListenerOEMV1 listener) { 240 if (listener == null) { 241 return; 242 } 243 if (mScrollListeners.isEmpty()) { 244 mRecyclerView.addOnScrollListener(mOnScrollListener); 245 } 246 mScrollListeners.add(listener); 247 } 248 249 @Override removeOnScrollListener(OnScrollListenerOEMV1 listener)250 public void removeOnScrollListener(OnScrollListenerOEMV1 listener) { 251 if (listener == null) { 252 return; 253 } 254 mScrollListeners.remove(listener); 255 if (mScrollListeners.isEmpty()) { 256 mRecyclerView.removeOnScrollListener(mOnScrollListener); 257 } 258 } 259 260 @Override clearOnScrollListeners()261 public void clearOnScrollListeners() { 262 if (!mScrollListeners.isEmpty()) { 263 mScrollListeners.clear(); 264 mRecyclerView.clearOnScrollListeners(); 265 } 266 } 267 268 @Override scrollToPosition(int position)269 public void scrollToPosition(int position) { 270 mRecyclerView.scrollToPosition(position); 271 } 272 273 @Override smoothScrollBy(int dx, int dy)274 public void smoothScrollBy(int dx, int dy) { 275 mRecyclerView.smoothScrollBy(dx, dy); 276 } 277 278 @Override smoothScrollToPosition(int position)279 public void smoothScrollToPosition(int position) { 280 mRecyclerView.smoothScrollToPosition(position); 281 } 282 283 @Override setHasFixedSize(boolean hasFixedSize)284 public void setHasFixedSize(boolean hasFixedSize) { 285 mRecyclerView.setHasFixedSize(hasFixedSize); 286 } 287 288 @Override hasFixedSize()289 public boolean hasFixedSize() { 290 return mRecyclerView.hasFixedSize(); 291 } 292 293 @Override setLayoutStyle(@ullable LayoutStyleOEMV1 layoutStyle)294 public void setLayoutStyle(@Nullable LayoutStyleOEMV1 layoutStyle) { 295 mLayoutStyle = layoutStyle; 296 297 int orientation = layoutStyle == null ? VERTICAL : layoutStyle.getOrientation(); 298 boolean reverseLayout = layoutStyle != null && layoutStyle.getReverseLayout(); 299 300 if (layoutStyle == null 301 || layoutStyle.getLayoutType() == LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR) { 302 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), 303 orientation, 304 reverseLayout)); 305 } else { 306 GridLayoutManager glm = new GridLayoutManager(getContext(), 307 layoutStyle.getSpanCount(), 308 orientation, 309 reverseLayout); 310 glm.setSpanSizeLookup(new SpanSizeLookup() { 311 @Override 312 public int getSpanSize(int position) { 313 return layoutStyle.getSpanSize(position); 314 } 315 }); 316 mRecyclerView.setLayoutManager(glm); 317 } 318 } 319 320 @Override getLayoutStyle()321 public LayoutStyleOEMV1 getLayoutStyle() { 322 return mLayoutStyle; 323 } 324 getView()325 public View getView() { 326 return this; 327 } 328 329 @Override findFirstCompletelyVisibleItemPosition()330 public int findFirstCompletelyVisibleItemPosition() { 331 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 332 if (layoutManager instanceof LinearLayoutManager) { 333 return ((LinearLayoutManager) layoutManager) 334 .findFirstCompletelyVisibleItemPosition(); 335 } 336 return 0; 337 } 338 339 @Override findFirstVisibleItemPosition()340 public int findFirstVisibleItemPosition() { 341 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 342 if (layoutManager instanceof LinearLayoutManager) { 343 return ((LinearLayoutManager) layoutManager) 344 .findFirstVisibleItemPosition(); 345 } 346 return 0; 347 } 348 349 @Override findLastCompletelyVisibleItemPosition()350 public int findLastCompletelyVisibleItemPosition() { 351 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 352 if (layoutManager instanceof LinearLayoutManager) { 353 return ((LinearLayoutManager) layoutManager) 354 .findLastCompletelyVisibleItemPosition(); 355 } 356 return 0; 357 } 358 359 @Override findLastVisibleItemPosition()360 public int findLastVisibleItemPosition() { 361 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 362 if (layoutManager instanceof LinearLayoutManager) { 363 return ((LinearLayoutManager) layoutManager) 364 .findLastVisibleItemPosition(); 365 } 366 return 0; 367 } 368 toInternalScrollState(int state)369 private static int toInternalScrollState(int state) { 370 /* default to RecyclerView.SCROLL_STATE_IDLE */ 371 int internalState = RecyclerViewOEMV1.SCROLL_STATE_IDLE; 372 switch (state) { 373 case RecyclerView.SCROLL_STATE_DRAGGING: 374 internalState = RecyclerViewOEMV1.SCROLL_STATE_DRAGGING; 375 break; 376 case RecyclerView.SCROLL_STATE_SETTLING: 377 internalState = RecyclerViewOEMV1.SCROLL_STATE_SETTLING; 378 break; 379 } 380 return internalState; 381 } 382 383 @Override getScrollState()384 public int getScrollState() { 385 return toInternalScrollState(mRecyclerView.getScrollState()); 386 } 387 388 @Override setContentDescription(CharSequence contentDescription)389 public void setContentDescription(CharSequence contentDescription) { 390 boolean rotaryScrollEnabled = contentDescription != null 391 && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription) 392 || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription)); 393 int orientation = getLayoutStyle() == null ? LinearLayout.VERTICAL 394 : getLayoutStyle().getOrientation(); 395 initRotaryScroll(mRecyclerView, rotaryScrollEnabled, orientation); 396 // Only change this view's content description when not related to rotary scroll. Don't 397 // change its content description when related to rotary scroll, because the content 398 // description should be set on its inner recyclerview in this case. 399 if (!rotaryScrollEnabled) { 400 super.setContentDescription(contentDescription); 401 } 402 } 403 createOrientationHelper()404 private OrientationHelper createOrientationHelper() { 405 if (mLayoutStyle.getOrientation() == LayoutStyleOEMV1.ORIENTATION_VERTICAL) { 406 return OrientationHelper.createVerticalHelper(mRecyclerView.getLayoutManager()); 407 } else { 408 return OrientationHelper.createHorizontalHelper(mRecyclerView.getLayoutManager()); 409 } 410 } 411 412 @Override getEndAfterPadding()413 public int getEndAfterPadding() { 414 if (mLayoutStyle == null) return 0; 415 return createOrientationHelper().getEndAfterPadding(); 416 } 417 418 @Override getStartAfterPadding()419 public int getStartAfterPadding() { 420 if (mLayoutStyle == null) return 0; 421 return createOrientationHelper().getStartAfterPadding(); 422 } 423 424 @Override getTotalSpace()425 public int getTotalSpace() { 426 if (mLayoutStyle == null) return 0; 427 return createOrientationHelper().getTotalSpace(); 428 } 429 430 @Override setPadding(int left, int top, int right, int bottom)431 public void setPadding(int left, int top, int right, int bottom) { 432 if (mScrollBar != null) { 433 int currentPosition = findFirstVisibleItemPosition(); 434 setScrollBarPadding(top, bottom); 435 // Maintain same index position after setting padding 436 scrollToPosition(currentPosition); 437 } 438 mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), 439 top, mRecyclerView.getPaddingRight(), bottom); 440 super.setPadding(left, 0, right, 0); 441 } 442 443 @Override getPaddingTop()444 public int getPaddingTop() { 445 return mRecyclerView.getPaddingTop(); 446 } 447 448 @Override getPaddingBottom()449 public int getPaddingBottom() { 450 return mRecyclerView.getPaddingBottom(); 451 } 452 453 @Override setPaddingRelative(int start, int top, int end, int bottom)454 public void setPaddingRelative(int start, int top, int end, int bottom) { 455 if (mScrollBar != null) { 456 int currentPosition = findFirstVisibleItemPosition(); 457 setScrollBarPadding(top, bottom); 458 // Maintain same index position after setting padding 459 scrollToPosition(currentPosition); 460 } 461 mRecyclerView.setPaddingRelative(0, top, 0, bottom); 462 super.setPaddingRelative(start, 0, end, 0); 463 } 464 setScrollBarPadding(int paddingTop, int paddingBottom)465 private void setScrollBarPadding(int paddingTop, int paddingBottom) { 466 if (mScrollBar != null) { 467 mScrollBar.setPadding(paddingTop, paddingBottom); 468 } 469 } 470 471 @Override onAttachedToWindow()472 protected void onAttachedToWindow() { 473 super.onAttachedToWindow(); 474 mCarUxRestrictionsUtil.register(mListener); 475 } 476 477 @Override onDetachedFromWindow()478 protected void onDetachedFromWindow() { 479 super.onDetachedFromWindow(); 480 mCarUxRestrictionsUtil.unregister(mListener); 481 } 482 483 @Override getRecyclerViewChildCount()484 public int getRecyclerViewChildCount() { 485 if (mRecyclerView.getLayoutManager() != null) { 486 return mRecyclerView.getLayoutManager().getChildCount(); 487 } else { 488 return 0; 489 } 490 } 491 492 @Override getRecyclerViewChildAt(int index)493 public View getRecyclerViewChildAt(int index) { 494 if (mRecyclerView.getLayoutManager() != null) { 495 return mRecyclerView.getLayoutManager().getChildAt(index); 496 } else { 497 return null; 498 } 499 } 500 501 @Override findViewHolderForAdapterPosition(int position)502 public ViewHolderOEMV1 findViewHolderForAdapterPosition(int position) { 503 ViewHolder viewHolder = mRecyclerView.findViewHolderForAdapterPosition(position); 504 if (viewHolder instanceof ViewHolderWrapper) { 505 return ((ViewHolderWrapper) viewHolder).getViewHolder(); 506 } 507 return null; 508 } 509 510 @Override findViewHolderForLayoutPosition(int position)511 public ViewHolderOEMV1 findViewHolderForLayoutPosition(int position) { 512 ViewHolder viewHolder = mRecyclerView.findViewHolderForLayoutPosition(position); 513 if (viewHolder instanceof ViewHolderWrapper) { 514 return ((ViewHolderWrapper) viewHolder).getViewHolder(); 515 } 516 return null; 517 } 518 519 @Override canScrollHorizontally(int direction)520 public boolean canScrollHorizontally(int direction) { 521 return mRecyclerView.canScrollHorizontally(direction); 522 } 523 524 @Override canScrollVertically(int direction)525 public boolean canScrollVertically(int direction) { 526 return mRecyclerView.canScrollVertically(direction); 527 } 528 529 @Override addOnChildAttachStateChangeListener( OnChildAttachStateChangeListenerOEMV1 listener)530 public void addOnChildAttachStateChangeListener( 531 OnChildAttachStateChangeListenerOEMV1 listener) { 532 if (listener == null) { 533 return; 534 } 535 if (mOnChildAttachStateChangeListeners.isEmpty()) { 536 mRecyclerView.addOnChildAttachStateChangeListener(mOnChildAttachStateChangeListener); 537 } 538 mOnChildAttachStateChangeListeners.add(listener); 539 } 540 541 @Override removeOnChildAttachStateChangeListener( OnChildAttachStateChangeListenerOEMV1 listener)542 public void removeOnChildAttachStateChangeListener( 543 OnChildAttachStateChangeListenerOEMV1 listener) { 544 if (listener == null) { 545 return; 546 } 547 mOnChildAttachStateChangeListeners.remove(listener); 548 if (mOnChildAttachStateChangeListeners.isEmpty()) { 549 mRecyclerView.removeOnChildAttachStateChangeListener(mOnChildAttachStateChangeListener); 550 } 551 } 552 553 @Override clearOnChildAttachStateChangeListener()554 public void clearOnChildAttachStateChangeListener() { 555 if (!mOnChildAttachStateChangeListeners.isEmpty()) { 556 mOnChildAttachStateChangeListeners.clear(); 557 mRecyclerView.clearOnChildAttachStateChangeListeners(); 558 } 559 } 560 561 @Override getChildLayoutPosition(View child)562 public int getChildLayoutPosition(View child) { 563 return mRecyclerView.getChildLayoutPosition(child); 564 } 565 566 /** 567 * If this view's {@code rotaryScrollEnabled} attribute is set to true, sets the content 568 * description so that the {@code RotaryService} will treat it as a scrollable container and 569 * initializes this view accordingly. 570 */ initRotaryScroll(@onNull ViewGroup recyclerView, boolean rotaryScrollEnabled, int orientation)571 private void initRotaryScroll(@NonNull ViewGroup recyclerView, 572 boolean rotaryScrollEnabled, 573 int orientation) { 574 if (rotaryScrollEnabled) { 575 setRotaryScrollEnabled( 576 recyclerView, /* isVertical= */ orientation == LinearLayout.VERTICAL); 577 } 578 579 // If rotary scrolling is enabled, set a generic motion event listener to convert 580 // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView 581 // knows how to handle. 582 recyclerView.setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> { 583 if (event.getAction() == MotionEvent.ACTION_SCROLL) { 584 if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) { 585 MotionEvent mouseEvent = MotionEvent.obtain(event); 586 mouseEvent.setSource(InputDevice.SOURCE_MOUSE); 587 recyclerView.onGenericMotionEvent(mouseEvent); 588 return true; 589 } 590 } 591 return false; 592 } : null); 593 594 // If rotary scrolling is enabled, mark this view as focusable. This view will be focused 595 // when no focusable elements are visible. 596 recyclerView.setFocusable(rotaryScrollEnabled); 597 598 // Focus this view before descendants so that the RotaryService can focus this view when it 599 // wants to. 600 recyclerView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 601 602 // Disable the default focus highlight. No highlight should appear when this view is 603 // focused. 604 recyclerView.setDefaultFocusHighlightEnabled(false); 605 606 // If rotary scrolling is enabled, set a focus change listener to highlight the scrollbar 607 // thumb when this recycler view is focused, i.e. when no focusable descendant is visible. 608 recyclerView.setOnFocusChangeListener(rotaryScrollEnabled ? (v, hasFocus) -> { 609 if (mScrollBar != null) mScrollBar.setHighlightThumb(hasFocus); 610 } : null); 611 612 // This recyclerView is a rotary container if it's not a scrollable container. 613 if (!rotaryScrollEnabled) { 614 recyclerView.setContentDescription(ROTARY_CONTAINER); 615 } 616 } 617 setRotaryScrollEnabled(@onNull View view, boolean isVertical)618 private static void setRotaryScrollEnabled(@NonNull View view, boolean isVertical) { 619 view.setContentDescription( 620 isVertical ? ROTARY_VERTICALLY_SCROLLABLE : ROTARY_HORIZONTALLY_SCROLLABLE); 621 } 622 623 private class UxRestrictionChangedListener implements 624 CarUxRestrictionsUtil.OnUxRestrictionsChangedListener { 625 626 @Override onRestrictionsChanged(@onNull CarUxRestrictions carUxRestrictions)627 public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) { 628 Adapter<?> adapter = mRecyclerView.getAdapter(); 629 if (adapter == null) return; 630 631 int maxItems = AdapterOEMV1.UNLIMITED; 632 if ((carUxRestrictions.getActiveRestrictions() 633 & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT) != 0) { 634 maxItems = carUxRestrictions.getMaxCumulativeContentItems(); 635 } 636 637 int originalCount = adapter.getItemCount(); 638 ((AdapterWrapper) adapter).getOEMAdapter().setMaxItems(maxItems); 639 int newCount = adapter.getItemCount(); 640 641 if (newCount == originalCount) { 642 return; 643 } 644 645 if (newCount < originalCount) { 646 adapter.notifyItemRangeRemoved(newCount, originalCount - newCount); 647 } else { 648 adapter.notifyItemRangeInserted(originalCount, newCount - originalCount); 649 } 650 } 651 } 652 } 653