Fix focus-search crash when TvLazyColumn contains empty TvLazyRow

When beyond-bounds modifier is called during focus search, it uses
last() instead of the safer lastOrNull() call when looking through
elements to add to the focus-search interval.

Updated the code to use lastOrNull and create an interval with
only one element to avoid the crash.

Bug: b/260299091

Test: Added unit test
Change-Id: I237c6ac5cfee4104aceda95b1c4a3e767db3689c
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
index 3de22a2..72f62aa 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
@@ -35,16 +35,14 @@
 @Composable
 fun LazyRowsAndColumns() {
     TvLazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) {
-        repeat((0 until rowsCount).count()) {
-            item { SampleLazyRow() }
-        }
+        items(rowsCount) { SampleLazyRow() }
     }
 }
 
 @Composable
 fun SampleLazyRow() {
     val colors = listOf(Color.Red, Color.Magenta, Color.Green, Color.Yellow, Color.Blue, Color.Cyan)
-    val backgroundColors = (0 until columnsCount).map { colors.random() }
+    val backgroundColors = List(columnsCount) { colors.random() }
 
     TvLazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
         backgroundColors.forEach { backgroundColor ->
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
index 336c09e..e320788 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
@@ -1098,6 +1098,7 @@
 }
 
 internal fun ComposeContentTestRule.keyPress(keyCode: Int, numberOfPresses: Int = 1) {
-    for (index in 0 until numberOfPresses)
+    repeat(numberOfPresses) {
         InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
-}
+    }
+}
\ No newline at end of file
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 2a8e3e2..55f74eb 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -16,11 +16,19 @@
 
 package androidx.tv.foundation.lazy.list
 
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -38,7 +46,9 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.LayoutDirection.Ltr
 import androidx.compose.ui.unit.LayoutDirection.Rtl
+import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
+import androidx.tv.foundation.lazy.grid.keyPress
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -460,6 +470,39 @@
         }
     }
 
+    @Test
+    fun emptyRowInColumn_focusSearchDoesNotCrash() {
+        val buttonFocusRequester = FocusRequester()
+        rule.setContent {
+            Column {
+                BasicText(
+                    text = "Outer button",
+                    Modifier.focusRequester(buttonFocusRequester).focusable())
+
+                TvLazyColumn {
+                    items(3) {
+                        if (it == 2) {
+                            // intentional empty row
+                            TvLazyRow(
+                                Modifier
+                                    .height(200.dp)
+                                    .width(2000.dp)) {}
+                        } else {
+                            TvLazyRow {
+                                items(30) {
+                                    Box(Modifier.size(200.dp)) { BasicText(text = it.toString()) }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle { buttonFocusRequester.requestFocus() }
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_DOWN)
+    }
+
     private fun ComposeContentTestRule.setLazyContent(
         size: Dp,
         firstVisibleItem: Int,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyBeyondBoundsModifier.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyBeyondBoundsModifier.kt
index c4777fb..7868efd 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyBeyondBoundsModifier.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyBeyondBoundsModifier.kt
@@ -86,7 +86,7 @@
         // We use a new interval each time because this function is re-entrant.
         var interval = beyondBoundsInfo.addInterval(
             state.firstVisibleItemIndex,
-            state.layoutInfo.visibleItemsInfo.last().index
+            state.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: state.firstVisibleItemIndex
         )
 
         var found: T? = null