Add material 3 Icon and Icon tests into androidx

Bug: b/259530410
Test: Run androidx.wear.compose.material3 tests
Change-Id: I8e06ab15f55eab28513445106da13a9fa7b6aac6
Relnote: N/A
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -84,6 +84,12 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class IconKt {
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/api/public_plus_experimental_current.txt b/wear/compose/compose-material3/api/public_plus_experimental_current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material3/api/public_plus_experimental_current.txt
@@ -84,6 +84,12 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class IconKt {
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -84,6 +84,12 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class IconKt {
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt
new file mode 100644
index 0000000..729aace
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class IconTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val testTag = "TestText"
+
+    @Test
+    fun vector_materialIconSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val vector = Icons.Filled.Menu
+        rule
+            .setContentWithThemeForSizeAssertions {
+                Icon(vector, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun vector_customIconSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+        val vector = ImageVector.Builder(
+            defaultWidth = width, defaultHeight = height,
+            viewportWidth = width.value, viewportHeight = height.value
+        ).build()
+        rule
+            .setContentWithThemeForSizeAssertions {
+                Icon(vector, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        rule
+            .setContentWithThemeForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                Icon(image, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                Icon(image, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val painter = ColorPainter(Color.Red)
+
+        rule
+            .setContentWithThemeForSizeAssertions {
+                Icon(painter, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                val bitmapPainter = BitmapPainter(image)
+                Icon(bitmapPainter, null)
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconScalesToFitSize() {
+        // Image with intrinsic size of 24dp
+        val width = 24.dp
+        val height = 24.dp
+        var expectedIntSize: IntSize? = null
+
+        rule
+            .setContentWithTheme {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(
+                    image,
+                    null,
+                    // Force Icon to be 50dp
+                    modifier = Modifier.requiredSize(50.dp).testTag(testTag),
+                    tint = Color.Unspecified
+                )
+                with(LocalDensity.current) {
+                    val dimension = 50.dp.roundToPx()
+                    expectedIntSize = IntSize(dimension, dimension)
+                }
+            }
+
+        rule.onNodeWithTag(testTag)
+            .captureToImage()
+            // The icon should be 50x50 and fill the whole size with red pixels
+            .assertPixels(expectedSize = expectedIntSize!!) {
+                Color.Red
+            }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconUnspecifiedTintColorIgnored() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentWithTheme {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(
+                    image,
+                    null,
+                    modifier = Modifier.testTag(testTag),
+                    tint = Color.Unspecified
+                )
+            }
+
+        // With no color provided for a tint, the icon should render the original pixels
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Red }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconSpecifiedTintColorApplied() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentWithTheme {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(
+                    image,
+                    null,
+                    modifier = Modifier.testTag(testTag),
+                    tint = Color.Blue
+                )
+            }
+
+        // With a tint color provided, all pixels should be blue
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun defaultSemanticsWhenContentDescriptionProvided() {
+        rule
+            .setContent {
+                Icon(
+                    bitmap = ImageBitmap(100, 100),
+                    contentDescription = "qwerty",
+                    modifier = Modifier.testTag(testTag)
+                )
+            }
+
+        rule.onNodeWithTag(testTag)
+            .assertContentDescriptionEquals("qwerty")
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
+    }
+
+    private fun createBitmapWithColor(
+        density: Density,
+        width: Int,
+        height: Int,
+        color: Color
+    ): ImageBitmap {
+        val size = Size(width.toFloat(), height.toFloat())
+        val image = ImageBitmap(width, height)
+        CanvasDrawScope().draw(
+            density,
+            LayoutDirection.Ltr,
+            Canvas(image),
+            size
+        ) {
+            drawRect(color)
+        }
+        return image
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
new file mode 100644
index 0000000..f427c75
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+val BigTestMaxWidth = 5000.dp
+val BigTestMaxHeight = 5000.dp
+
+fun ComposeContentTestRule.setContentWithThemeForSizeAssertions(
+    parentMaxWidth: Dp = BigTestMaxWidth,
+    parentMaxHeight: Dp = BigTestMaxHeight,
+    useUnmergedTree: Boolean = false,
+    content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+    setContent {
+        MaterialTheme {
+            Box {
+                Box(
+                    Modifier
+                        .sizeIn(
+                            maxWidth = parentMaxWidth,
+                            maxHeight = parentMaxHeight
+                        )
+                        .testTag("containerForSizeAssertion")
+                ) {
+                    content()
+                }
+            }
+        }
+    }
+
+    return onNodeWithTag("containerForSizeAssertion", useUnmergedTree)
+}
+
+fun ComposeContentTestRule.setContentWithTheme(
+    modifier: Modifier = Modifier,
+    composable: @Composable BoxScope.() -> Unit
+) {
+    setContent {
+        MaterialTheme {
+            Box(modifier = modifier, content = composable)
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt
new file mode 100644
index 0000000..2113a27
--- /dev/null
+++ b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+
+/**
+ * Icon component that draws [imageVector] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param imageVector [ImageVector] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no
+ *  tint is applied
+ */
+@Composable
+fun Icon(
+    imageVector: ImageVector,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+    Icon(
+        painter = rememberVectorPainter(imageVector),
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint
+    )
+}
+
+/**
+ * Icon component that draws [bitmap] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param bitmap [ImageBitmap] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no
+ *  tint is applied
+ */
+@Composable
+fun Icon(
+    bitmap: ImageBitmap,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+    val painter = remember(bitmap) { BitmapPainter(bitmap) }
+    Icon(
+        painter = painter,
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint
+    )
+}
+
+/**
+ * Icon component that draws a [painter] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param painter [Painter] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [painter]. If [Color.Unspecified] is provided, then no
+ *  tint is applied
+ */
+@Composable
+fun Icon(
+    painter: Painter,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+    androidx.wear.compose.materialcore.Icon(
+        painter = painter,
+        contentDescription = contentDescription,
+        tint = tint,
+        modifier = modifier
+    )
+}
\ No newline at end of file