Merge "Rename VirtualSessionState to be CaptureSessionState and update comments." into androidx-main
diff --git a/annotation/annotation-experimental/lint-baseline.xml b/annotation/annotation-experimental/lint-baseline.xml
new file mode 100644
index 0000000..47688b5
--- /dev/null
+++ b/annotation/annotation-experimental/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target("
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/OptIn.kt"/>
+ </issue>
+
+</issues>
diff --git a/annotation/annotation/lint-baseline.xml b/annotation/annotation/lint-baseline.xml
new file mode 100644
index 0000000..a148b58
--- /dev/null
+++ b/annotation/annotation/lint-baseline.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target("
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/Keep.kt"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target("
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/NonNull.kt"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target(METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE)"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/Nullable.kt"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target(TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE)"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/RequiresApi.kt"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+ errorLine1="@java.lang.annotation.Target("
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/annotation/RestrictTo.kt"/>
+ </issue>
+
+</issues>
diff --git a/appsearch/appsearch/lint-baseline.xml b/appsearch/appsearch/lint-baseline.xml
new file mode 100644
index 0000000..9eebc3d
--- /dev/null
+++ b/appsearch/appsearch/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type java.util.Set<java.util.Set<java.lang.Integer>>; expected int"
+ errorLine1=" @SetSchemaRequest.AppSearchSupportedPermission @NonNull"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/appsearch/app/GetSchemaResponse.java"/>
+ </issue>
+
+</issues>
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index 7466d0f..fb018a7 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -51,6 +51,8 @@
" Use `adb root`."
}
+ getInstalledPackageInfo(packageName) // throws clearly if not installed
+
val startTime = System.nanoTime()
val scope = MacrobenchmarkScope(packageName, launchWithClearTask = true)
@@ -72,10 +74,12 @@
killProcessBlock.invoke()
try {
userspaceTrace("compile $packageName") {
+ var iteration = 1
compilationMode.resetAndCompile(
packageName = packageName,
killProcessBlock = killProcessBlock
) {
+ scope.iteration = iteration++
profileBlock(scope)
}
}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index e63fb5f..fe156f1 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -16,6 +16,7 @@
package androidx.benchmark.macro
+import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE
import android.content.pm.PackageManager
import android.os.Build
@@ -39,18 +40,24 @@
import androidx.tracing.trace
import java.io.File
+/**
+ * Get package ApplicationInfo, throw if not found
+ */
@Suppress("DEPRECATION")
-internal fun checkErrors(packageName: String): ConfigurationError.SuppressionState? {
+internal fun getInstalledPackageInfo(packageName: String): ApplicationInfo {
val pm = InstrumentationRegistry.getInstrumentation().context.packageManager
-
- val applicationInfo = try {
- pm.getApplicationInfo(packageName, 0)
+ try {
+ return pm.getApplicationInfo(packageName, 0)
} catch (notFoundException: PackageManager.NameNotFoundException) {
throw AssertionError(
"Unable to find target package $packageName, is it installed?",
notFoundException
)
}
+}
+
+internal fun checkErrors(packageName: String): ConfigurationError.SuppressionState? {
+ val applicationInfo = getInstalledPackageInfo(packageName)
val errorNotProfileable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
applicationInfo.isNotProfileableByShell()
diff --git a/benchmark/integration-tests/macrobenchmark/build.gradle b/benchmark/integration-tests/macrobenchmark/build.gradle
index b9738c4..e823480 100644
--- a/benchmark/integration-tests/macrobenchmark/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -41,6 +41,7 @@
androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
androidTestImplementation(project(":internal-testutils-macrobenchmark"))
androidTestImplementation(project(":tracing:tracing-ktx"))
+ androidTestImplementation(libs.kotlinTest)
androidTestImplementation(libs.testRules)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
similarity index 77%
rename from benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
rename to benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
index 0ae84d3..cb321f5 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
@@ -25,6 +25,10 @@
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import java.io.File
+import kotlin.test.assertFailsWith
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
@@ -32,24 +36,41 @@
@LargeTest
@SdkSuppress(minSdkVersion = 29)
@OptIn(ExperimentalBaselineProfilesApi::class)
-class BaselineProfileFilterTest {
+class BaselineProfileRuleTest {
@get:Rule
val baselineRule = BaselineProfileRule()
@Test
- fun baselineProfilesFilter() {
+ fun appNotInstalled() {
+ val error = assertFailsWith<AssertionError> {
+ baselineRule.collectBaselineProfile(
+ packageName = "fake.package.not.installed",
+ profileBlock = {
+ fail("not expected")
+ }
+ )
+ }
+ println(error.message)
+ assertTrue(error.message!!.contains("Unable to find target package"))
+ }
+
+ @Test
+ fun filter() {
assumeTrue(Shell.isSessionRooted())
+ var nextIteration = 1
// Collects the baseline profile
baselineRule.collectBaselineProfile(
packageName = PACKAGE_NAME,
packageFilters = listOf(PACKAGE_NAME),
profileBlock = {
+ assertEquals(nextIteration++, iteration)
startActivityAndWait(Intent(ACTION))
device.waitForIdle()
}
)
+ assertEquals(4, nextIteration)
// Asserts the output of the baseline profile
val lines = File(Outputs.outputDirectory, BASELINE_PROFILE_OUTPUT_FILE_NAME).readLines()
@@ -72,6 +93,6 @@
// according to the patter `<class>_<method>-baseline-prof.txt`. Changes for class and
// method names should be reflected here in order for the test to succeed.
private const val BASELINE_PROFILE_OUTPUT_FILE_NAME =
- "BaselineProfileFilterTest_baselineProfilesFilter-baseline-prof.txt"
+ "BaselineProfileRuleTest_filter-baseline-prof.txt"
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 0a8392a..26c4b7d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -202,9 +202,6 @@
// Broken in 7.0.0-alpha15 due to b/180408990
disable.add("RestrictedApi")
- // Reenable after upgradingto 7.1.0-beta01
- disable.add("SupportAnnotationUsage")
-
// Provide stricter enforcement for project types intended to run on a device.
if (extension.type.compilationTarget == CompilationTarget.DEVICE) {
fatal.add("Assert")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
index 344be48..c0ca9a5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -198,8 +198,8 @@
check(vmOptions.exists()) {
"Invalid Studio vm options file location: ${vmOptions.canonicalPath}"
}
- val processBuilder = ProcessBuilder().apply {
- redirectErrorStream(true)
+ ProcessBuilder().apply {
+ inheritIO()
with(platformUtilities) { command(launchCommandArguments) }
val additionalStudioEnvironmentProperties = mapOf(
@@ -218,14 +218,7 @@
// Append to the existing environment variables set by gradlew and the user.
environment().putAll(additionalStudioEnvironmentProperties)
- }
- val process = processBuilder.start()
- process.waitFor()
- // Can't just use inheritIO due to https://github.com/gradle/gradle/issues/16719
- val outputText = process.stdOutText()
- println(outputText)
- check(process.exitValue() == 0) {
- "Studio failed to start:\n$outputText"
+ start()
}
}
@@ -319,10 +312,3 @@
override val vmOptions
get() = supportRootFolder.resolve("playground-common/studio.vmoptions")
}
-
-/**
- * Returns the contents of the process's standard output stream as a string
- */
-fun Process.stdOutText(): String {
- return inputStream.bufferedReader().use { it.readText() }
-}
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index d575250ea..ea45943 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -8,6 +8,7 @@
-x lintDebug \
-x lintWithExpandProjectionDebug \
-x lintWithoutExpandProjectionDebug \
+ -x lintWithNullAwareTypeConverterDebug \
-x lintReport \
-x verifyDependencyVersions \
listTaskOutputs \
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
new file mode 100644
index 0000000..acec75e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @VisibleForTesting"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt"/>
+ </issue>
+
+</issues>
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
similarity index 84%
rename from camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt
rename to camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
index d80c7b3..49f2220 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
@@ -20,8 +20,8 @@
import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
import android.util.Size
import android.view.Surface
-import androidx.camera.core.SurfaceEffect
import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.DeferrableSurface
import androidx.camera.core.impl.ImageFormatConstants
@@ -51,7 +51,7 @@
@RunWith(AndroidJUnit4::class)
@LargeTest
@SdkSuppress(minSdkVersion = 21)
-class DefaultSurfaceEffectTest {
+class DefaultSurfaceProcessorTest {
companion object {
private const val WIDTH = 640
@@ -88,7 +88,7 @@
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(testCameraRule, testCameraIdListRule)
- private lateinit var surfaceEffect: DefaultSurfaceEffect
+ private lateinit var surfaceProcessor: DefaultSurfaceProcessor
private lateinit var cameraDeviceHolder: CameraUtil.CameraDeviceHolder
private lateinit var renderOutput: RenderOutput<*>
private val inputSurfaceRequestsToClose = mutableListOf<SurfaceRequest>()
@@ -102,9 +102,9 @@
if (::renderOutput.isInitialized) {
renderOutput.release()
}
- if (::surfaceEffect.isInitialized) {
- surfaceEffect.release()
- surfaceEffect.awaitReleased(10_000L)
+ if (::surfaceProcessor.isInitialized) {
+ surfaceProcessor.release()
+ surfaceProcessor.awaitReleased(10_000L)
}
for (surfaceRequest in inputSurfaceRequestsToClose) {
surfaceRequest.deferrableSurface.close()
@@ -114,18 +114,18 @@
@Test
fun release_closeAllSurfaceOutputs(): Unit = runBlocking {
// Arrange.
- createSurfaceEffect()
+ createSurfaceProcessor()
val surfaceOutput1 = createSurfaceOutput()
- surfaceEffect.onOutputSurface(surfaceOutput1)
+ surfaceProcessor.onOutputSurface(surfaceOutput1)
val surfaceOutput2 = createSurfaceOutput()
- surfaceEffect.onOutputSurface(surfaceOutput2)
+ surfaceProcessor.onOutputSurface(surfaceOutput2)
- surfaceEffect.idle()
+ surfaceProcessor.idle()
// Act.
- surfaceEffect.release()
- surfaceEffect.awaitReleased(5000)
+ surfaceProcessor.release()
+ surfaceProcessor.awaitReleased(5000)
// Assert.
assertThat(surfaceOutput1.isClosed).isTrue()
@@ -135,12 +135,12 @@
@Test
fun callOnInputSurfaceAfterReleased_willNotProvideSurface() {
// Arrange.
- createSurfaceEffect()
+ createSurfaceProcessor()
val surfaceRequest = createInputSurfaceRequest()
// Act.
- surfaceEffect.release()
- surfaceEffect.onInputSurface(surfaceRequest)
+ surfaceProcessor.release()
+ surfaceProcessor.onInputSurface(surfaceRequest)
// Assert.
try {
@@ -154,13 +154,13 @@
@Test
fun callOnOutputSurfaceAfterReleased_closeSurfaceOutput(): Unit = runBlocking {
// Arrange.
- createSurfaceEffect()
+ createSurfaceProcessor()
val surfaceOutput = createSurfaceOutput()
// Act.
- surfaceEffect.release()
- surfaceEffect.awaitReleased()
- surfaceEffect.onOutputSurface(surfaceOutput)
+ surfaceProcessor.release()
+ surfaceProcessor.awaitReleased()
+ surfaceProcessor.onOutputSurface(surfaceOutput)
// Assert.
assertThat(surfaceOutput.isClosed).isTrue()
@@ -169,14 +169,14 @@
@Test
fun requestCloseAfterOnOutputSurface_closeSurfaceOutput() {
// Arrange.
- createSurfaceEffect()
+ createSurfaceProcessor()
val surfaceOutput = createSurfaceOutput()
// Act.
- surfaceEffect.onOutputSurface(surfaceOutput)
- surfaceEffect.idle()
+ surfaceProcessor.onOutputSurface(surfaceOutput)
+ surfaceProcessor.idle()
surfaceOutput.requestClose()
- surfaceEffect.idle()
+ surfaceProcessor.idle()
// Assert.
assertThat(surfaceOutput.isClosed).isTrue()
@@ -185,13 +185,13 @@
@Test
fun requestCloseBeforeOnOutputSurface_closeSurfaceOutput() {
// Arrange.
- createSurfaceEffect()
+ createSurfaceProcessor()
val surfaceOutput = createSurfaceOutput()
// Act.
surfaceOutput.requestClose()
- surfaceEffect.onOutputSurface(surfaceOutput)
- surfaceEffect.idle()
+ surfaceProcessor.onOutputSurface(surfaceOutput)
+ surfaceProcessor.idle()
// Assert.
assertThat(surfaceOutput.isClosed).isTrue()
@@ -225,7 +225,7 @@
fun createByInvalidShaderString_throwException() {
val shaderProvider = createCustomShaderProvider(shaderString = "Invalid shader")
assertThrows(IllegalArgumentException::class.java) {
- createSurfaceEffect(shaderProvider)
+ createSurfaceProcessor(shaderProvider)
}
}
@@ -234,7 +234,7 @@
val shaderProvider =
createCustomShaderProvider(exceptionToThrow = RuntimeException("Failed Shader"))
assertThrows(IllegalArgumentException::class.java) {
- createSurfaceEffect(shaderProvider)
+ createSurfaceProcessor(shaderProvider)
}
}
@@ -242,7 +242,7 @@
fun createByIncorrectSamplerName_throwException() {
val shaderProvider = createCustomShaderProvider(samplerVarName = "_mySampler_")
assertThrows(IllegalArgumentException::class.java) {
- createSurfaceEffect(shaderProvider)
+ createSurfaceProcessor(shaderProvider)
}
}
@@ -250,7 +250,7 @@
fun createByIncorrectFragCoordsName_throwException() {
val shaderProvider = createCustomShaderProvider(fragCoordsVarName = "_myFragCoords_")
assertThrows(IllegalArgumentException::class.java) {
- createSurfaceEffect(shaderProvider)
+ createSurfaceProcessor(shaderProvider)
}
}
@@ -258,10 +258,10 @@
outputType: OutputType,
shaderProvider: ShaderProvider = ShaderProvider.DEFAULT
) {
- createSurfaceEffect(shaderProvider)
+ createSurfaceProcessor(shaderProvider)
// Prepare input
val inputSurfaceRequest = createInputSurfaceRequest()
- surfaceEffect.onInputSurface(inputSurfaceRequest)
+ surfaceProcessor.onInputSurface(inputSurfaceRequest)
val inputDeferrableSurface = inputSurfaceRequest.deferrableSurface
val inputSurface = inputDeferrableSurface.surface.await()
openCameraAndSetRepeating(inputSurface)
@@ -272,7 +272,7 @@
// Prepare output
renderOutput = RenderOutput.createRenderOutput(outputType)
val surfaceOutput = createSurfaceOutput(renderOutput.surface)
- surfaceEffect.onOutputSurface(surfaceOutput)
+ surfaceProcessor.onOutputSurface(surfaceOutput)
// Assert.
assertThat(renderOutput.await(/*imageCount=*/5, /*timeoutInMs=*/10_000L)).isTrue()
@@ -289,8 +289,10 @@
)
}
- private fun createSurfaceEffect(shaderProvider: ShaderProvider = ShaderProvider.DEFAULT) {
- surfaceEffect = DefaultSurfaceEffect(shaderProvider)
+ private fun createSurfaceProcessor(shaderProvider: ShaderProvider = ShaderProvider.DEFAULT) {
+ surfaceProcessor = DefaultSurfaceProcessor(
+ shaderProvider
+ )
}
private fun createInputSurfaceRequest(): SurfaceRequest {
@@ -302,7 +304,7 @@
private fun createSurfaceOutput(surface: Surface = mock(Surface::class.java)) =
SurfaceOutputImpl(
surface,
- SurfaceEffect.PREVIEW,
+ SurfaceProcessor.PREVIEW,
ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
Size(WIDTH, HEIGHT),
USE_SURFACE_TEXTURE_TRANSFORM,
@@ -334,11 +336,11 @@
}
}
- private fun DefaultSurfaceEffect.idle() {
+ private fun DefaultSurfaceProcessor.idle() {
HandlerUtil.waitForLooperToIdle(mGlHandler)
}
- private suspend fun DefaultSurfaceEffect.awaitReleased(timeoutMs: Long = 5000L) {
+ private suspend fun DefaultSurfaceProcessor.awaitReleased(timeoutMs: Long = 5000L) {
withTimeoutOrNull(timeoutMs) {
while (true) {
delay(500L)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
index b4d7822..2445aa0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
@@ -39,7 +39,7 @@
*/
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY)
- @IntDef(flag = true, value = {SurfaceEffect.PREVIEW, SurfaceEffect.VIDEO_CAPTURE,
+ @IntDef(flag = true, value = {SurfaceProcessor.PREVIEW, SurfaceProcessor.VIDEO_CAPTURE,
ImageEffect.IMAGE_CAPTURE})
@interface Targets {
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
index 1025eb6..ba6b61f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
@@ -16,7 +16,7 @@
package androidx.camera.core;
-import static androidx.camera.core.SurfaceEffect.PREVIEW;
+import static androidx.camera.core.SurfaceProcessor.PREVIEW;
import static androidx.core.util.Preconditions.checkArgument;
import androidx.annotation.NonNull;
@@ -81,7 +81,7 @@
* Adds a {@link CameraEffect} with its targets.
*
* @param targets on which the effect will be applied. CameraX only supports
- * {@link SurfaceEffect#PREVIEW} for now.
+ * {@link SurfaceProcessor#PREVIEW} for now.
* @param cameraEffect the effect implementation.
* @throws IllegalArgumentException if the configuration is illegal.
*/
@@ -91,11 +91,11 @@
@NonNull CameraEffect cameraEffect) {
checkArgument(!mEffects.containsKey(targets), "The target already has an effect");
checkArgument(targets == PREVIEW, "Only allows PREVIEW target.");
- if (cameraEffect instanceof SurfaceEffect) {
+ if (cameraEffect instanceof SurfaceProcessor) {
mEffects.put(targets, cameraEffect);
} else {
throw new UnsupportedOperationException(
- "CameraX only supports SurfaceEffect for now.");
+ "CameraX only supports SurfaceProcessor for now.");
}
return this;
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index fd52b2c..062cdea 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -95,8 +95,8 @@
import androidx.camera.core.processing.Node;
import androidx.camera.core.processing.SettableSurface;
import androidx.camera.core.processing.SurfaceEdge;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectNode;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorNode;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import androidx.lifecycle.LifecycleOwner;
@@ -194,10 +194,10 @@
private Size mSurfaceSize;
@Nullable
- private SurfaceEffectInternal mSurfaceEffect;
+ private SurfaceProcessorInternal mSurfaceProcessor;
@Nullable
- private SurfaceEffectNode mNode;
+ private SurfaceProcessorNode mNode;
/**
* Creates a new preview use case from the given configuration.
@@ -214,9 +214,9 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
SessionConfig.Builder createPipeline(@NonNull String cameraId, @NonNull PreviewConfig config,
@NonNull Size resolution) {
- // Build pipeline with node if effect is set. Eventually we will move all the code to
+ // Build pipeline with node if processor is set. Eventually we will move all the code to
// createPipelineWithNode.
- if (mSurfaceEffect != null) {
+ if (mSurfaceProcessor != null) {
return createPipelineWithNode(cameraId, config, resolution);
}
@@ -303,16 +303,16 @@
@NonNull Size resolution) {
// Check arguments
Threads.checkMainThread();
- Preconditions.checkNotNull(mSurfaceEffect);
+ Preconditions.checkNotNull(mSurfaceProcessor);
CameraInternal camera = getCamera();
Preconditions.checkNotNull(camera);
clearPipeline();
// Create nodes and edges.
- mNode = new SurfaceEffectNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceEffect);
+ mNode = new SurfaceProcessorNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceProcessor);
SettableSurface cameraSurface = new SettableSurface(
- SurfaceEffect.PREVIEW,
+ SurfaceProcessor.PREVIEW,
resolution,
ImageFormat.PRIVATE,
new Matrix(),
@@ -339,7 +339,7 @@
}
/**
- * Sets a {@link SurfaceEffectInternal}.
+ * Sets a {@link SurfaceProcessorInternal}.
*
* <p>Internal API invoked by {@link CameraUseCaseAdapter}. {@link #createPipeline} uses the
* value to setup post-processing pipeline.
@@ -347,20 +347,20 @@
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
- public void setEffect(@Nullable SurfaceEffectInternal surfaceEffect) {
- mSurfaceEffect = surfaceEffect;
+ public void setProcessor(@Nullable SurfaceProcessorInternal surfaceProcessor) {
+ mSurfaceProcessor = surfaceProcessor;
}
/**
- * Gets the {@link SurfaceEffectInternal} for testing.
+ * Gets the {@link SurfaceProcessorInternal} for testing.
*
* @hide
*/
@Nullable
@VisibleForTesting
@RestrictTo(Scope.LIBRARY_GROUP)
- public SurfaceEffectInternal getEffect() {
- return mSurfaceEffect;
+ public SurfaceProcessorInternal getProcessor() {
+ return mSurfaceProcessor;
}
/**
@@ -372,7 +372,7 @@
cameraSurface.close();
mSessionDeferrableSurface = null;
}
- SurfaceEffectNode node = mNode;
+ SurfaceProcessorNode node = mNode;
if (node != null) {
node.release();
mNode = null;
@@ -390,7 +390,7 @@
// Not to add deferrable surface if the surface provider is not set, as that means the
// surface will never be provided. For simplicity, the same rule also applies to
- // SurfaceEffectNode and CaptureProcessor cases, since no surface provider also means no
+ // SurfaceProcessorNode and CaptureProcessor cases, since no surface provider also means no
// output target for these two cases.
if (mSurfaceProvider != null) {
sessionConfigBuilder.addSurface(mSessionDeferrableSurface);
@@ -447,7 +447,7 @@
SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
if (cameraInternal != null && surfaceProvider != null && cropRect != null
&& surfaceRequest != null) {
- // TODO: when SurfaceEffectNode exists, use SettableSurface.setRotationDegrees(int)
+ // TODO: when SurfaceProcessorNode exists, use SettableSurface.setRotationDegrees(int)
// instead. However, this requires PreviewView to rely on relative rotation but not
// target rotation.
surfaceRequest.updateTransformationInfo(SurfaceRequest.TransformationInfo.of(cropRect,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index 9930739..6bf27c5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -38,7 +38,7 @@
* lifecycle of the {@link Surface}.
*
* @hide
- * @see SurfaceEffect#onOutputSurface(SurfaceOutput)
+ * @see SurfaceProcessor#onOutputSurface(SurfaceOutput)
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public interface SurfaceOutput {
@@ -63,10 +63,10 @@
* This field indicates that what purpose the {@link Surface} will be used for.
*
* <ul>
- * <li>{@link SurfaceEffect#PREVIEW} if the {@link Surface} will be used for {@link Preview}.
- * <li>{@link SurfaceEffect#VIDEO_CAPTURE} if the {@link Surface} will be used for video
+ * <li>{@link SurfaceProcessor#PREVIEW} if the {@link Surface} will be used for {@link Preview}.
+ * <li>{@link SurfaceProcessor#VIDEO_CAPTURE} if the {@link Surface} will be used for video
* capture.
- * <li>{@link SurfaceEffect#PREVIEW} | {@link SurfaceEffect#VIDEO_CAPTURE} if the output
+ * <li>{@link SurfaceProcessor#PREVIEW} | {@link SurfaceProcessor#VIDEO_CAPTURE} if the output
* {@link Surface} will be used for sharing a single stream for both preview and video capture.
* </ul>
*/
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
similarity index 98%
rename from camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
rename to camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
index 5367124..fd55b33 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
@@ -34,7 +34,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface SurfaceEffect extends CameraEffect {
+public interface SurfaceProcessor extends CameraEffect {
/**
* Bitmask option to indicate that CameraX applies this effect to {@link Preview}.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index 44c76b4..d5a76f2 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -109,7 +109,7 @@
/**
* Sets the {@link EffectBundle} for the {@link UseCase}s.
*
- * <p>Once set, CameraX will use the {@link SurfaceEffect}s to process the outputs of
+ * <p>Once set, CameraX will use the {@link SurfaceProcessor}s to process the outputs of
* the {@link UseCase}s.
*
* @hide
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index c8ea8f4..a19cccb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -37,7 +37,7 @@
import androidx.camera.core.ImageCapture;
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.UseCase;
import androidx.camera.core.ViewPort;
import androidx.camera.core.impl.AttachedSurfaceInfo;
@@ -52,8 +52,8 @@
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.UseCaseConfigFactory;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectWithExecutor;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
@@ -451,17 +451,18 @@
for (Map.Entry<Integer, CameraEffect> entry : effectBundle.getEffects().entrySet()) {
CameraEffect effect = entry.getValue();
int targets = entry.getKey();
- if (effect instanceof SurfaceEffect) {
- effectsWithExecutors.put(targets, new SurfaceEffectWithExecutor(
- (SurfaceEffect) effect, executor));
+ if (effect instanceof SurfaceProcessor) {
+ effectsWithExecutors.put(targets, new SurfaceProcessorWithExecutor(
+ (SurfaceProcessor) effect, executor));
}
}
}
// Set effects on the UseCases. This also removes existing effects if necessary.
for (UseCase useCase : useCases) {
if (useCase instanceof Preview) {
- ((Preview) useCase).setEffect(
- (SurfaceEffectInternal) effectsWithExecutors.get(SurfaceEffect.PREVIEW));
+ ((Preview) useCase).setProcessor(
+ (SurfaceProcessorInternal) effectsWithExecutors.get(
+ SurfaceProcessor.PREVIEW));
}
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
similarity index 92%
rename from camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
rename to camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
index 7d127aa..8be1285 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
@@ -25,8 +25,8 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
-import androidx.camera.core.SurfaceEffect;
import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -40,13 +40,13 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A default implementation of {@link SurfaceEffect}.
+ * A default implementation of {@link SurfaceProcessor}.
*
* <p> This implementation simply copies the frame from the source to the destination with the
* transformation defined in {@link SurfaceOutput#updateTransformMatrix}.
*/
@RequiresApi(21)
-public class DefaultSurfaceEffect implements SurfaceEffectInternal,
+public class DefaultSurfaceProcessor implements SurfaceProcessorInternal,
SurfaceTexture.OnFrameAvailableListener {
private final OpenGlRenderer mGlRenderer;
@VisibleForTesting
@@ -64,18 +64,18 @@
// Only access this on GL thread.
private int mInputSurfaceCount = 0;
- /** Constructs DefaultSurfaceEffect */
- public DefaultSurfaceEffect() {
+ /** Constructs {@link DefaultSurfaceProcessor} with default shaders. */
+ public DefaultSurfaceProcessor() {
this(ShaderProvider.DEFAULT);
}
/**
- * Constructs DefaultSurfaceEffect
+ * Constructs {@link DefaultSurfaceProcessor} with custom shaders.
*
* @param shaderProvider custom shader provider for OpenGL rendering.
* @throws IllegalArgumentException if the shaderProvider provides invalid shader.
*/
- public DefaultSurfaceEffect(@NonNull ShaderProvider shaderProvider) {
+ public DefaultSurfaceProcessor(@NonNull ShaderProvider shaderProvider) {
mGlThread = new HandlerThread("GL Thread");
mGlThread.start();
mGlHandler = new Handler(mGlThread.getLooper());
@@ -133,7 +133,7 @@
}
/**
- * Release the DefaultSurfaceEffect
+ * Release the {@link DefaultSurfaceProcessor}.
*/
@Override
public void release() {
@@ -199,7 +199,7 @@
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
- throw new IllegalStateException("Failed to create DefaultSurfaceEffect", cause);
+ throw new IllegalStateException("Failed to create DefaultSurfaceProcessor", cause);
}
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
index 7fe2ce6..dad0080 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
@@ -36,9 +36,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraEffect;
-import androidx.camera.core.SurfaceEffect;
import androidx.camera.core.SurfaceOutput;
import androidx.camera.core.SurfaceOutput.GlTransformOptions;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.SurfaceRequest.TransformationInfo;
import androidx.camera.core.UseCase;
@@ -61,7 +61,7 @@
* an external surface consumer:
* <pre>
* {@code PreviewView}(surface provider) <--> {@link SurfaceRequest} <--> {@link SettableSurface}
- * <--> {@link SurfaceOutput} --> {@link SurfaceEffect}(surface consumer)
+ * <--> {@link SurfaceOutput} --> {@link SurfaceProcessor}(surface consumer)
* </pre>
*
* <p>For the full workflow, please see {@code SettableSurfaceTest
@@ -240,7 +240,7 @@
* Creates a {@link SurfaceOutput} that is linked to this {@link SettableSurface}.
*
* <p>The {@link SurfaceOutput} is for providing a surface to an external target such
- * as {@link SurfaceEffect}.
+ * as {@link SurfaceProcessor}.
*
* <p>This method returns a {@link ListenableFuture} that completes when the
* {@link SettableSurface#getSurface()} completes. The {@link SurfaceOutput} contains the
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
index 3edf244..cc64c21 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
@@ -59,7 +59,7 @@
return null;
}
- /** A default provider that will use the default shader code without any effect. */
+ /** A default provider that will use the default shader code without any post-processing. */
ShaderProvider DEFAULT = new ShaderProvider() {
// Use default implementation.
};
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
deleted file mode 100644
index ea3640d1..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2022 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.camera.core.processing;
-
-import androidx.camera.core.SurfaceEffect;
-
-/**
- * An internal {@link SurfaceEffect} that is releasable.
- *
- * <p>Note: the implementation of this interface must be thread-safe. e.g. methods can be
- * safely invoked on any thread.
- */
-public interface SurfaceEffectInternal extends SurfaceEffect {
-
- /**
- * Releases all the resources allocated by the effect.
- *
- * <p>An effect created by CameraX should be released by CameraX when it's no longer needed.
- * On the other hand, an external effect should not be released by CameraX, because CameraX
- * not does know if the effect will be needed again. In that case, the app is responsible for
- * releasing the effect. It should be able to keep the effect alive across multiple
- * attach/detach cycles if it's necessary.
- *
- * @see Node#release()
- */
- void release();
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
deleted file mode 100644
index c3113f2..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2022 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.camera.core.processing;
-
-import static androidx.core.util.Preconditions.checkState;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.camera.core.SurfaceEffect;
-import androidx.camera.core.SurfaceOutput;
-import androidx.camera.core.SurfaceRequest;
-
-import java.util.concurrent.Executor;
-
-/**
- * A wrapper of a pair of {@link SurfaceEffect} and {@link Executor}.
- *
- * <p> Wraps the external {@link SurfaceEffect} and {@link Executor} provided by the app. It
- * makes sure that CameraX always invoke the {@link SurfaceEffect} on the correct {@link Executor}.
- */
-public class SurfaceEffectWithExecutor implements SurfaceEffectInternal {
-
- @NonNull
- private final SurfaceEffect mSurfaceEffect;
- @NonNull
- private final Executor mExecutor;
-
- public SurfaceEffectWithExecutor(
- @NonNull SurfaceEffect surfaceEffect,
- @NonNull Executor executor) {
- checkState(!(surfaceEffect instanceof SurfaceEffectInternal),
- "SurfaceEffectInternal should always be thread safe. Do not wrap.");
- mSurfaceEffect = surfaceEffect;
- mExecutor = executor;
- }
-
- @NonNull
- @VisibleForTesting
- public SurfaceEffect getSurfaceEffect() {
- return mSurfaceEffect;
- }
-
- @NonNull
- @VisibleForTesting
- public Executor getExecutor() {
- return mExecutor;
- }
-
- @Override
- public void onInputSurface(@NonNull SurfaceRequest request) {
- mExecutor.execute(() -> mSurfaceEffect.onInputSurface(request));
- }
-
- @Override
- public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
- mExecutor.execute(() -> mSurfaceEffect.onOutputSurface(surfaceOutput));
- }
-
- @Override
- public void release() {
- // No-op. External SurfaceEffect should not be released by CameraX.
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
index e5dd98b..6a2894a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
@@ -36,8 +36,8 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Consumer;
@@ -144,7 +144,7 @@
}
/**
- * Asks the {@link SurfaceEffect} implementation to stopping writing to the {@link Surface}.
+ * Asks the {@link SurfaceProcessor} implementation to stopping writing to the {@link Surface}.
*/
public void requestClose() {
AtomicReference<Consumer<Event>> eventListenerRef = new AtomicReference<>();
@@ -168,7 +168,7 @@
// The executor might be invoked after the SurfaceOutputImpl is closed. This
// happens if the #close() is called after the synchronized block above but
// before the line below.
- Logger.d(TAG, "Effect executor closed. Close request not posted.", e);
+ Logger.d(TAG, "Processor executor closed. Close request not posted.", e);
}
}
}
@@ -207,7 +207,7 @@
}
/**
- * This method can be invoked by the effect implementation on any thread.
+ * This method can be invoked by the processor implementation on any thread.
*
* @inheritDoc
*/
@@ -243,7 +243,7 @@
}
/**
- * This method can be invoked by the effect implementation on any thread.
+ * This method can be invoked by the processor implementation on any thread.
*/
@AnyThread
@Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java
new file mode 100644
index 0000000..47cbe62
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 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.camera.core.processing;
+
+import androidx.camera.core.SurfaceProcessor;
+
+/**
+ * An internal {@link SurfaceProcessor} that is releasable.
+ *
+ * <p>Note: the implementation of this interface must be thread-safe. e.g. methods can be
+ * safely invoked on any thread.
+ */
+public interface SurfaceProcessorInternal extends SurfaceProcessor {
+
+ /**
+ * Releases all the resources allocated by the processor.
+ *
+ * <p>An processor created by CameraX should be released by CameraX when it's no longer needed.
+ * On the other hand, an external processor should not be released by CameraX, because CameraX
+ * not does know if the processor will be needed again. In that case, the app is responsible for
+ * releasing the processor. It should be able to keep the processor alive across multiple
+ * attach/detach cycles if it's necessary.
+ *
+ * @see Node#release()
+ */
+ void release();
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
similarity index 87%
rename from camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
rename to camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
index 90ceb7a..fd1e4bcf8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
@@ -35,9 +35,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.core.SurfaceEffect;
import androidx.camera.core.SurfaceOutput;
import androidx.camera.core.SurfaceOutput.GlTransformOptions;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.utils.Threads;
@@ -46,11 +46,11 @@
import androidx.core.util.Preconditions;
/**
- * A {@link Node} implementation that wraps around the public {@link SurfaceEffect} interface.
+ * A {@link Node} implementation that wraps around the public {@link SurfaceProcessor} interface.
*
* <p>Responsibilities:
* <ul>
- * <li>Calculating transformation and passing it to the {@link SurfaceEffect}.
+ * <li>Calculating transformation and passing it to the {@link SurfaceProcessor}.
* <li>Tracking the state of previously calculate specification and only recreate the pipeline
* when necessary.
* </ul>
@@ -58,11 +58,11 @@
@RequiresApi(api = 21)
// TODO(b/233627260): remove once implemented.
@SuppressWarnings("UnusedVariable")
-public class SurfaceEffectNode implements Node<SurfaceEdge, SurfaceEdge> {
+public class SurfaceProcessorNode implements Node<SurfaceEdge, SurfaceEdge> {
private final GlTransformOptions mGlTransformOptions;
@NonNull
- final SurfaceEffectInternal mSurfaceEffect;
+ final SurfaceProcessorInternal mSurfaceProcessor;
@NonNull
final CameraInternal mCameraInternal;
// Guarded by main thread.
@@ -72,18 +72,18 @@
private SurfaceEdge mInputEdge;
/**
- * Constructs the surface effect node
+ * Constructs the {@link SurfaceProcessorNode}.
*
* @param cameraInternal the associated camera instance.
* @param glTransformOptions the OpenGL transformation options.
- * @param surfaceEffect the interface to wrap around.
+ * @param surfaceProcessor the interface to wrap around.
*/
- public SurfaceEffectNode(@NonNull CameraInternal cameraInternal,
+ public SurfaceProcessorNode(@NonNull CameraInternal cameraInternal,
@NonNull GlTransformOptions glTransformOptions,
- @NonNull SurfaceEffectInternal surfaceEffect) {
+ @NonNull SurfaceProcessorInternal surfaceProcessor) {
mCameraInternal = cameraInternal;
mGlTransformOptions = glTransformOptions;
- mSurfaceEffect = surfaceEffect;
+ mSurfaceProcessor = surfaceProcessor;
}
/**
@@ -99,7 +99,7 @@
mInputEdge = inputEdge;
SettableSurface inputSurface = inputEdge.getSurfaces().get(0);
SettableSurface outputSurface = createOutputSurface(inputSurface);
- sendSurfacesToEffectWhenReady(inputSurface, outputSurface);
+ sendSurfacesToProcessorWhenReady(inputSurface, outputSurface);
mOutputEdge = SurfaceEdge.create(singletonList(outputSurface));
return mOutputEdge;
}
@@ -156,7 +156,7 @@
return outputSurface;
}
- private void sendSurfacesToEffectWhenReady(@NonNull SettableSurface input,
+ private void sendSurfacesToProcessorWhenReady(@NonNull SettableSurface input,
@NonNull SettableSurface output) {
SurfaceRequest surfaceRequest = input.createSurfaceRequest(mCameraInternal);
Futures.addCallback(output.createSurfaceOutputFuture(mGlTransformOptions,
@@ -166,16 +166,16 @@
@Override
public void onSuccess(@Nullable SurfaceOutput surfaceOutput) {
Preconditions.checkNotNull(surfaceOutput);
- mSurfaceEffect.onOutputSurface(surfaceOutput);
- mSurfaceEffect.onInputSurface(surfaceRequest);
+ mSurfaceProcessor.onOutputSurface(surfaceOutput);
+ mSurfaceProcessor.onInputSurface(surfaceRequest);
setupSurfaceUpdatePipeline(input, surfaceRequest, output, surfaceOutput);
}
@Override
public void onFailure(@NonNull Throwable t) {
- // Do not send surfaces to effect if the downstream provider (e.g. the app)
- // fails to provide a Surface. Instead, notify the consumer that the
- // Surface will not be provided.
+ // Do not send surfaces to the processor if the downstream provider (e.g.
+ // the app) fails to provide a Surface. Instead, notify the consumer that
+ // the Surface will not be provided.
surfaceRequest.willNotProvideSurface();
}
}, mainThreadExecutor());
@@ -201,11 +201,11 @@
*/
@Override
public void release() {
- mSurfaceEffect.release();
+ mSurfaceProcessor.release();
mainThreadExecutor().execute(() -> {
if (mOutputEdge != null) {
for (SettableSurface surface : mOutputEdge.getSurfaces()) {
- // The output DeferrableSurface will later be terminated by the effect.
+ // The output DeferrableSurface will later be terminated by the processor.
surface.close();
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java
new file mode 100644
index 0000000..87a8644
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 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.camera.core.processing;
+
+import static androidx.core.util.Preconditions.checkState;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
+import androidx.camera.core.SurfaceRequest;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A wrapper of a pair of {@link SurfaceProcessor} and {@link Executor}.
+ *
+ * <p> Wraps the external {@link SurfaceProcessor} and {@link Executor} provided by the app. It
+ * makes sure that CameraX always invoke the {@link SurfaceProcessor} on the correct
+ * {@link Executor}.
+ */
+public class SurfaceProcessorWithExecutor implements SurfaceProcessorInternal {
+
+ @NonNull
+ private final SurfaceProcessor mSurfaceProcessor;
+ @NonNull
+ private final Executor mExecutor;
+
+ public SurfaceProcessorWithExecutor(
+ @NonNull SurfaceProcessor surfaceProcessor,
+ @NonNull Executor executor) {
+ checkState(!(surfaceProcessor instanceof SurfaceProcessorInternal),
+ "SurfaceProcessorInternal should always be thread safe. Do not wrap.");
+ mSurfaceProcessor = surfaceProcessor;
+ mExecutor = executor;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ public SurfaceProcessor getProcessor() {
+ return mSurfaceProcessor;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ public Executor getExecutor() {
+ return mExecutor;
+ }
+
+ @Override
+ public void onInputSurface(@NonNull SurfaceRequest request) {
+ mExecutor.execute(() -> mSurfaceProcessor.onInputSurface(request));
+ }
+
+ @Override
+ public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
+ mExecutor.execute(() -> mSurfaceProcessor.onOutputSurface(surfaceOutput));
+ }
+
+ @Override
+ public void release() {
+ // No-op. External SurfaceProcessor should not be released by CameraX.
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
index f23fbd7..621932c 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
@@ -23,6 +23,7 @@
import androidx.camera.testing.fakes.FakeCameraFactory
import androidx.camera.testing.fakes.FakeCameraInfoInternal
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.ExecutionException
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,8 +31,6 @@
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.LinkedHashSet
-import java.util.concurrent.ExecutionException
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
index 616fb90..de2b346 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
@@ -17,9 +17,9 @@
package androidx.camera.core
import android.os.Build
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,21 +42,24 @@
@Test(expected = IllegalArgumentException::class)
fun addMoreThanOnePreviewEffect_throwsException() {
- val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+ val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
EffectBundle.Builder(mainThreadExecutor())
- .addEffect(PREVIEW, surfaceEffect)
- .addEffect(PREVIEW, surfaceEffect)
+ .addEffect(PREVIEW, surfaceProcessor)
+ .addEffect(PREVIEW, surfaceProcessor)
}
@Test
fun addPreviewEffect_hasPreviewEffect() {
// Arrange.
- val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+ val surfaceProcessor =
+ FakeSurfaceProcessor(mainThreadExecutor())
// Act.
val effectBundle = EffectBundle.Builder(mainThreadExecutor())
- .addEffect(PREVIEW, surfaceEffect)
+ .addEffect(PREVIEW, surfaceProcessor)
.build()
// Assert.
- assertThat(effectBundle.effects.values.first() as SurfaceEffect).isEqualTo(surfaceEffect)
+ assertThat(effectBundle.effects.values.first() as SurfaceProcessor).isEqualTo(
+ surfaceProcessor
+ )
}
}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 06db2e8..5f64956 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -35,14 +35,14 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.core.processing.SurfaceEffectInternal
+import androidx.camera.core.processing.SurfaceProcessorInternal
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraXUtil
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
import androidx.camera.testing.fakes.FakeCameraFactory
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
import androidx.camera.testing.fakes.FakeUseCase
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
@@ -217,10 +217,13 @@
@Test
fun bindAndUnbindPreview_surfacesPropagated() {
// Arrange.
- val effect = FakeSurfaceEffectInternal(mainThreadExecutor(), false)
+ val processor = FakeSurfaceProcessorInternal(
+ mainThreadExecutor(),
+ false
+ )
// Act: create pipeline in Preview and provide Surface.
- val preview = createPreviewPipelineAndAttachEffect(effect)
+ val preview = createPreviewPipelineAndAttachProcessor(processor)
val surfaceRequest = preview.mCurrentSurfaceRequest!!
var appSurfaceReadyToRelease = false
surfaceRequest.provideSurface(appSurface, mainThreadExecutor()) {
@@ -229,26 +232,27 @@
shadowOf(getMainLooper()).idle()
// Assert: surfaceOutput received.
- assertThat(effect.surfaceOutput).isNotNull()
- assertThat(effect.isReleased).isFalse()
- assertThat(effect.isOutputSurfaceRequestedToClose).isFalse()
- assertThat(effect.isInputSurfaceReleased).isFalse()
+ assertThat(processor.surfaceOutput).isNotNull()
+ assertThat(processor.isReleased).isFalse()
+ assertThat(processor.isOutputSurfaceRequestedToClose).isFalse()
+ assertThat(processor.isInputSurfaceReleased).isFalse()
assertThat(appSurfaceReadyToRelease).isFalse()
- // effect surface is provided to camera.
- assertThat(preview.sessionConfig.surfaces[0].surface.get()).isEqualTo(effect.inputSurface)
+ // processor surface is provided to camera.
+ assertThat(preview.sessionConfig.surfaces[0].surface.get())
+ .isEqualTo(processor.inputSurface)
// Act: unbind Preview.
preview.onDetached()
shadowOf(getMainLooper()).idle()
- // Assert: effect and effect surface is released.
- assertThat(effect.isReleased).isTrue()
- assertThat(effect.isOutputSurfaceRequestedToClose).isTrue()
- assertThat(effect.isInputSurfaceReleased).isTrue()
+ // Assert: processor and processor surface is released.
+ assertThat(processor.isReleased).isTrue()
+ assertThat(processor.isOutputSurfaceRequestedToClose).isTrue()
+ assertThat(processor.isInputSurfaceReleased).isTrue()
assertThat(appSurfaceReadyToRelease).isFalse()
// Act: close SurfaceOutput
- effect.surfaceOutput!!.close()
+ processor.surfaceOutput!!.close()
shadowOf(getMainLooper()).idle()
assertThat(appSurfaceReadyToRelease).isTrue()
}
@@ -256,8 +260,10 @@
@Test
fun invokedErrorListener_recreatePipeline() {
// Arrange: create pipeline and get a reference of the SessionConfig.
- val effect = FakeSurfaceEffectInternal(mainThreadExecutor())
- val preview = createPreviewPipelineAndAttachEffect(effect)
+ val processor = FakeSurfaceProcessorInternal(
+ mainThreadExecutor()
+ )
+ val preview = createPreviewPipelineAndAttachProcessor(processor)
val originalSessionConfig = preview.sessionConfig
// Act: invoke the error listener.
@@ -432,13 +438,13 @@
return Pair(surfaceRequest!!, transformationInfo!!)
}
- private fun createPreviewPipelineAndAttachEffect(
- surfaceEffect: SurfaceEffectInternal?
+ private fun createPreviewPipelineAndAttachProcessor(
+ surfaceProcessor: SurfaceProcessorInternal?
): Preview {
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.build()
- preview.effect = surfaceEffect
+ preview.processor = surfaceProcessor
preview.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
val previewConfig = PreviewConfig(
cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context).getConfig(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
index 27af2cf..7c67cdd 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
@@ -26,6 +26,7 @@
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraFactory
import androidx.test.core.app.ApplicationProvider
+import java.io.File
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -39,7 +40,6 @@
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
index ac6541c..01bd6ee 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
@@ -22,12 +22,12 @@
import androidx.exifinterface.media.ExifInterface.FLAG_FLASH_FIRED
import androidx.exifinterface.media.ExifInterface.FLAG_FLASH_NO_FLASH_FUNCTION
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.TimeUnit
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index c3e5bda..bd9048e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -25,7 +25,7 @@
import androidx.camera.core.EffectBundle
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
import androidx.camera.core.UseCase
import androidx.camera.core.ViewPort
import androidx.camera.core.impl.CameraConfig
@@ -36,10 +36,10 @@
import androidx.camera.core.impl.OptionsBundle
import androidx.camera.core.impl.UseCaseConfigFactory
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.core.processing.SurfaceEffectWithExecutor
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
import androidx.camera.testing.fakes.FakeUseCase
import androidx.camera.testing.fakes.FakeUseCaseConfig
import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
@@ -69,7 +69,7 @@
@org.robolectric.annotation.Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
class CameraUseCaseAdapterTest {
- private lateinit var surfaceEffect: FakeSurfaceEffect
+ private lateinit var surfaceProcessor: FakeSurfaceProcessor
private lateinit var effectBundle: EffectBundle
private lateinit var executor: ExecutorService
@@ -84,14 +84,14 @@
fakeCamera = FakeCamera(CAMERA_ID)
useCaseConfigFactory = FakeUseCaseConfigFactory()
fakeCameraSet.add(fakeCamera)
- surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+ surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
executor = Executors.newSingleThreadExecutor()
- effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceEffect).build()
+ effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceProcessor).build()
}
@After
fun tearDown() {
- surfaceEffect.cleanUp()
+ surfaceProcessor.cleanUp()
executor.shutdown()
}
@@ -619,14 +619,14 @@
val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
// Act: update use cases with effects bundle
CameraUseCaseAdapter.updateEffects(effectBundle, listOf(preview))
- // Assert: preview has effect wrapped with the right executor.
- val previewEffect = preview.effect as SurfaceEffectWithExecutor
- assertThat(previewEffect.surfaceEffect).isEqualTo(surfaceEffect)
- assertThat(previewEffect.executor).isEqualTo(executor)
+ // Assert: preview has processor wrapped with the right executor.
+ val previewProcessor = preview.processor as SurfaceProcessorWithExecutor
+ assertThat(previewProcessor.processor).isEqualTo(surfaceProcessor)
+ assertThat(previewProcessor.executor).isEqualTo(executor)
// Act: update again with null effects bundle
CameraUseCaseAdapter.updateEffects(null, listOf(preview))
- // Assert: preview no longer has effects.
- assertThat(preview.effect).isNull()
+ // Assert: preview no longer has processors.
+ assertThat(preview.processor).isNull()
}
private fun createCoexistingRequiredRuleCameraConfig(): CameraConfig {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
index 1514784..a409ec67 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
@@ -24,12 +24,12 @@
import android.os.Looper.getMainLooper
import android.util.Size
import android.view.Surface
-import androidx.camera.core.SurfaceEffect
import androidx.camera.core.SurfaceOutput
import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.SurfaceRequest
-import androidx.camera.core.SurfaceRequest.TransformationInfo
import androidx.camera.core.SurfaceRequest.Result.RESULT_REQUEST_CANCELLED
+import androidx.camera.core.SurfaceRequest.TransformationInfo
import androidx.camera.core.impl.DeferrableSurface.SurfaceClosedException
import androidx.camera.core.impl.DeferrableSurface.SurfaceUnavailableException
import androidx.camera.core.impl.ImmediateSurface
@@ -70,7 +70,7 @@
@Before
fun setUp() {
settableSurface = SettableSurface(
- SurfaceEffect.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
+ SurfaceProcessor.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
Matrix(), true, Rect(), 0, false
)
fakeSurfaceTexture = SurfaceTexture(0)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
index f056808..e6d0b85 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
@@ -22,9 +22,9 @@
import android.os.Looper
import android.util.Size
import android.view.Surface
-import androidx.camera.core.SurfaceEffect
import androidx.camera.core.SurfaceOutput.GlTransformOptions
import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.impl.utils.TransformUtils.sizeToRect
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import com.google.common.truth.Truth.assertThat
@@ -46,7 +46,7 @@
class SurfaceOutputImplTest {
companion object {
- private const val TARGET = SurfaceEffect.PREVIEW
+ private const val TARGET = SurfaceProcessor.PREVIEW
private const val FORMAT = PixelFormat.RGBA_8888
private val OUTPUT_SIZE = Size(640, 480)
private val INPUT_SIZE = Size(640, 480)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
similarity index 83%
rename from camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
rename to camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
index dea1462..abbbda5 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
@@ -23,10 +23,10 @@
import android.os.Looper.getMainLooper
import android.util.Size
import android.view.Surface
-import androidx.camera.core.SurfaceEffect.PREVIEW
import androidx.camera.core.SurfaceOutput.GlTransformOptions
import androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING
import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor.PREVIEW
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.SurfaceRequest.TransformationInfo
import androidx.camera.core.impl.utils.TransformUtils.is90or270
@@ -34,7 +34,7 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.core.impl.utils.futures.Futures
import androidx.camera.testing.fakes.FakeCamera
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -46,12 +46,12 @@
import org.robolectric.annotation.internal.DoNotInstrument
/**
- * Unit tests for [SurfaceEffectNode].
+ * Unit tests for [SurfaceProcessorNode].
*/
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class SurfaceEffectNodeTest {
+class SurfaceProcessorNodeTest {
companion object {
private const val TARGET = PREVIEW
@@ -61,10 +61,10 @@
private val CROP_RECT = Rect(0, 0, 600, 400)
}
- private lateinit var surfaceEffectInternal: FakeSurfaceEffectInternal
+ private lateinit var surfaceProcessorInternal: FakeSurfaceProcessorInternal
private lateinit var appSurface: Surface
private lateinit var appSurfaceTexture: SurfaceTexture
- private lateinit var node: SurfaceEffectNode
+ private lateinit var node: SurfaceProcessorNode
private lateinit var inputEdge: SurfaceEdge
private lateinit var outputSurfaceRequest: SurfaceRequest
private var outputTransformInfo: TransformationInfo? = null
@@ -73,14 +73,14 @@
fun setup() {
appSurfaceTexture = SurfaceTexture(0)
appSurface = Surface(appSurfaceTexture)
- surfaceEffectInternal = FakeSurfaceEffectInternal(mainThreadExecutor())
+ surfaceProcessorInternal = FakeSurfaceProcessorInternal(mainThreadExecutor())
}
@After
fun tearDown() {
appSurfaceTexture.release()
appSurface.release()
- surfaceEffectInternal.release()
+ surfaceProcessorInternal.release()
if (::node.isInitialized) {
node.release()
}
@@ -96,7 +96,7 @@
@Test
fun transformInput_useSurfaceTextureTransform_outputHasTheSameProperty() {
// Arrange.
- createSurfaceEffectNode()
+ createSurfaceProcessorNode()
createInputEdge()
val inputSurface = inputEdge.surfaces[0]
@@ -120,7 +120,7 @@
val cropRect = Rect(200, 100, 600, 400)
for (rotationDegrees in arrayOf(0, 90, 180, 270)) {
// Arrange.
- createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+ createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
createInputEdge(
size = rectToSize(cropRect),
cropRect = cropRect,
@@ -153,7 +153,7 @@
fun transformInput_applyCropRotateAndMirroring_outputHasNoMirroring() {
for (mirroring in arrayOf(false, true)) {
// Arrange.
- createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+ createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
createInputEdge(mirroring = mirroring)
// Act.
@@ -173,7 +173,7 @@
@Test
fun transformInput_applyCropRotateAndMirroring_initialTransformInfoIsPropagated() {
// Arrange.
- createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+ createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
createInputEdge(rotationDegrees = 90, cropRect = Rect(0, 0, 600, 400))
// Act.
@@ -182,9 +182,9 @@
createOutputSurfaceRequestAndProvideSurface(outputSurface)
shadowOf(getMainLooper()).idle()
- // Assert: surfaceOutput of SurfaceEffect will consume the initial rotation degrees and
+ // Assert: surfaceOutput of SurfaceProcessor will consume the initial rotation degrees and
// output surface will receive 0 degrees.
- assertThat(surfaceEffectInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
+ assertThat(surfaceProcessorInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
assertThat(outputTransformInfo!!.rotationDegrees).isEqualTo(0)
assertThat(outputTransformInfo!!.cropRect).isEqualTo(Rect(0, 0, 400, 600))
}
@@ -192,7 +192,7 @@
@Test
fun setRotationToInput_applyCropRotateAndMirroring_rotationIsPropagated() {
// Arrange.
- createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+ createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
createInputEdge(rotationDegrees = 90)
val inputSurface = inputEdge.surfaces[0]
val outputEdge = node.transform(inputEdge)
@@ -204,16 +204,16 @@
inputSurface.rotationDegrees = 270
shadowOf(getMainLooper()).idle()
- // Assert: surfaceOutput of SurfaceEffect will consume the initial rotation degrees and
+ // Assert: surfaceOutput of SurfaceProcessor will consume the initial rotation degrees and
// output surface will receive the remaining degrees.
- assertThat(surfaceEffectInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
+ assertThat(surfaceProcessorInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
assertThat(outputTransformInfo!!.rotationDegrees).isEqualTo(180)
}
@Test
fun provideSurfaceToOutput_surfaceIsPropagatedE2E() {
// Arrange.
- createSurfaceEffectNode()
+ createSurfaceProcessorNode()
createInputEdge()
val inputSurface = inputEdge.surfaces[0]
val outputEdge = node.transform(inputEdge)
@@ -223,15 +223,15 @@
outputSurface.setProvider(Futures.immediateFuture(appSurface))
shadowOf(getMainLooper()).idle()
- // Assert: effect receives app Surface. CameraX receives effect Surface.
- assertThat(surfaceEffectInternal.outputSurface).isEqualTo(appSurface)
- assertThat(inputSurface.surface.get()).isEqualTo(surfaceEffectInternal.inputSurface)
+ // Assert: processor receives app Surface. CameraX receives processor Surface.
+ assertThat(surfaceProcessorInternal.outputSurface).isEqualTo(appSurface)
+ assertThat(inputSurface.surface.get()).isEqualTo(surfaceProcessorInternal.inputSurface)
}
@Test
- fun releaseNode_effectIsReleased() {
+ fun releaseNode_processorIsReleased() {
// Arrange.
- createSurfaceEffectNode()
+ createSurfaceProcessorNode()
createInputEdge()
val outputSurface = node.transform(inputEdge).surfaces[0]
outputSurface.setProvider(Futures.immediateFuture(appSurface))
@@ -241,9 +241,9 @@
node.release()
shadowOf(getMainLooper()).idle()
- // Assert: effect is released and has requested effect to close the SurfaceOutput
- assertThat(surfaceEffectInternal.isReleased).isTrue()
- assertThat(surfaceEffectInternal.isOutputSurfaceRequestedToClose).isTrue()
+ // Assert: processor is released and has requested processor to close the SurfaceOutput
+ assertThat(surfaceProcessorInternal.isReleased).isTrue()
+ assertThat(surfaceProcessorInternal.isOutputSurfaceRequestedToClose).isTrue()
}
private fun createInputEdge(
@@ -269,10 +269,14 @@
inputEdge = SurfaceEdge.create(listOf(surface))
}
- private fun createSurfaceEffectNode(
+ private fun createSurfaceProcessorNode(
glTransformOptions: GlTransformOptions = USE_SURFACE_TEXTURE_TRANSFORM
) {
- node = SurfaceEffectNode(FakeCamera(), glTransformOptions, surfaceEffectInternal)
+ node = SurfaceProcessorNode(
+ FakeCamera(),
+ glTransformOptions,
+ surfaceProcessorInternal
+ )
}
private fun createOutputSurfaceRequestAndProvideSurface(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
similarity index 71%
rename from camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
rename to camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
index 6a33afe..b07183a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
@@ -21,13 +21,13 @@
import android.os.HandlerThread
import android.os.Looper.getMainLooper
import android.util.Size
-import androidx.camera.core.SurfaceEffect
import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.testing.fakes.FakeCamera
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
import com.google.common.truth.Truth.assertThat
import java.lang.Thread.currentThread
import java.util.concurrent.Executor
@@ -42,12 +42,12 @@
import org.robolectric.annotation.internal.DoNotInstrument
/**
- * Unit tests for [SurfaceEffectWithExecutor].
+ * Unit tests for [SurfaceProcessorWithExecutor].
*/
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class SurfaceEffectWithExecutorTest {
+class SurfaceProcessorWithExecutorTest {
companion object {
private val SIZE = Size(640, 480)
@@ -69,30 +69,32 @@
}
@Test(expected = IllegalStateException::class)
- fun initWithSurfaceEffectInternal_throwsException() {
- SurfaceEffectWithExecutor(
- FakeSurfaceEffectInternal(mainThreadExecutor()),
+ fun initWithSurfaceProcessorInternal_throwsException() {
+ SurfaceProcessorWithExecutor(
+ FakeSurfaceProcessorInternal(mainThreadExecutor()),
mainThreadExecutor()
)
}
@Test
- fun invokeEffect_invokedOnEffectExecutor() {
+ fun invokeProcessor_invokedOnProcessorExecutor() {
// Arrange: track which thread the methods are invoked on.
var onInputSurfaceInvokedThread: Thread? = null
var onOutputSurfaceInvokedThread: Thread? = null
- val effectWithExecutor = SurfaceEffectWithExecutor(object : SurfaceEffect {
- override fun onInputSurface(request: SurfaceRequest) {
- onInputSurfaceInvokedThread = currentThread()
- }
+ val processorWithExecutor =
+ SurfaceProcessorWithExecutor(object :
+ SurfaceProcessor {
+ override fun onInputSurface(request: SurfaceRequest) {
+ onInputSurfaceInvokedThread = currentThread()
+ }
- override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
- onOutputSurfaceInvokedThread = currentThread()
- }
- }, executor)
+ override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
+ onOutputSurfaceInvokedThread = currentThread()
+ }
+ }, executor)
// Act: invoke methods.
- effectWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false))
- effectWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
+ processorWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false))
+ processorWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
shadowOf(getMainLooper()).idle()
shadowOf(executorThread.looper).idle()
// Assert: it's the executor thread.
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 35e8e77..0bb2cbe 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -26,18 +26,18 @@
import androidx.camera.core.CameraXConfig
import androidx.camera.core.EffectBundle
import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
import androidx.camera.core.UseCaseGroup
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.core.processing.SurfaceEffectWithExecutor
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
import androidx.camera.testing.fakes.FakeCameraFactory
import androidx.camera.testing.fakes.FakeCameraInfoInternal
import androidx.camera.testing.fakes.FakeLifecycleOwner
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
import androidx.concurrent.futures.await
import androidx.test.core.app.ApplicationProvider
@@ -79,9 +79,9 @@
fun bindUseCaseGroupWithEffect_effectIsSetOnUseCase() {
// Arrange.
ProcessCameraProvider.configureInstance(FakeAppConfig.create())
- val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+ val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
val effectBundle =
- EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceEffect).build()
+ EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceProcessor).build()
val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview)
.setEffectBundle(effectBundle).build()
@@ -95,8 +95,8 @@
)
// Assert.
- val useCaseEffect = (preview.effect as SurfaceEffectWithExecutor).surfaceEffect
- assertThat(useCaseEffect).isEqualTo(surfaceEffect)
+ val useCaseProcessor = (preview.processor as SurfaceProcessorWithExecutor).processor
+ assertThat(useCaseProcessor).isEqualTo(surfaceProcessor)
}
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
similarity index 90%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
rename to camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
index 8a3d05d..b8d6e160 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
@@ -23,18 +23,18 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.core.SurfaceEffect;
import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.impl.DeferrableSurface;
import java.util.concurrent.Executor;
/**
- * Fake {@link SurfaceEffect} used in tests.
+ * Fake {@link SurfaceProcessor} used in tests.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class FakeSurfaceEffect implements SurfaceEffect {
+public class FakeSurfaceProcessor implements SurfaceProcessor {
final SurfaceTexture mSurfaceTexture;
final Surface mInputSurface;
@@ -52,9 +52,9 @@
Surface mOutputSurface;
/**
- * Creates a {@link SurfaceEffect} that closes the {@link SurfaceOutput} automatically.
+ * Creates a {@link SurfaceProcessor} that closes the {@link SurfaceOutput} automatically.
*/
- public FakeSurfaceEffect(@NonNull Executor executor) {
+ public FakeSurfaceProcessor(@NonNull Executor executor) {
this(executor, true);
}
@@ -65,7 +65,7 @@
* {@link SurfaceOutput#close()} to avoid the "Completer GCed"
* error in {@link DeferrableSurface}.
*/
- FakeSurfaceEffect(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
+ FakeSurfaceProcessor(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
mSurfaceTexture = new SurfaceTexture(0);
mInputSurface = new Surface(mSurfaceTexture);
mExecutor = executor;
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
similarity index 74%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
rename to camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
index 933db05..d1a7e5c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
@@ -20,29 +20,31 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.camera.core.processing.SurfaceEffectInternal;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
import java.util.concurrent.Executor;
/**
- * Fake {@link SurfaceEffectInternal} used in tests.
+ * Fake {@link SurfaceProcessorInternal} used in tests.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class FakeSurfaceEffectInternal extends FakeSurfaceEffect implements SurfaceEffectInternal {
+public class FakeSurfaceProcessorInternal extends FakeSurfaceProcessor implements
+ SurfaceProcessorInternal {
private boolean mIsReleased;
/**
* {@inheritDoc}
*/
- public FakeSurfaceEffectInternal(@NonNull Executor executor) {
+ public FakeSurfaceProcessorInternal(@NonNull Executor executor) {
this(executor, true);
}
/**
* {@inheritDoc}
*/
- public FakeSurfaceEffectInternal(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
+ public FakeSurfaceProcessorInternal(@NonNull Executor executor,
+ boolean autoCloseSurfaceOutput) {
super(executor, autoCloseSurfaceOutput);
mIsReleased = false;
}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
index 9c73beb..10e77fd 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
@@ -29,9 +29,9 @@
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.io.File
import org.junit.Test
import org.junit.runner.RunWith
-import java.io.File
@SmallTest
@RunWith(AndroidJUnit4::class)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
index b0406800..5c7b847 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
@@ -26,6 +26,7 @@
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.rule.GrantPermissionRule
+import java.util.concurrent.Callable
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -35,7 +36,6 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
-import java.util.concurrent.Callable
@LargeTest
@RunWith(AndroidJUnit4::class)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
index 45ae669..af3ed21 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
@@ -24,6 +24,9 @@
import androidx.test.filters.SmallTest
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
+import java.lang.ref.PhantomReference
+import java.lang.ref.ReferenceQueue
+import java.nio.ByteBuffer
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -34,9 +37,6 @@
import kotlinx.coroutines.withTimeout
import org.junit.Test
import org.junit.runner.RunWith
-import java.lang.ref.PhantomReference
-import java.lang.ref.ReferenceQueue
-import java.nio.ByteBuffer
@RunWith(AndroidJUnit4::class)
@SmallTest
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 69d84b3..b23e20e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -65,7 +65,7 @@
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.UseCase;
import androidx.camera.core.ViewPort;
@@ -94,11 +94,11 @@
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.core.internal.ThreadConfig;
-import androidx.camera.core.processing.DefaultSurfaceEffect;
+import androidx.camera.core.processing.DefaultSurfaceProcessor;
import androidx.camera.core.processing.SettableSurface;
import androidx.camera.core.processing.SurfaceEdge;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectNode;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorNode;
import androidx.camera.video.StreamInfo.StreamState;
import androidx.camera.video.impl.VideoCaptureConfig;
import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
@@ -158,22 +158,22 @@
private static final boolean HAS_IMAGE_CAPTURE_QUIRK =
DeviceQuirks.get(ImageCaptureFailedWhenVideoCaptureIsBoundQuirk.class) != null;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @SuppressWarnings("WeakerAccess") // Synthetic access
DeferrableSurface mDeferrableSurface;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @SuppressWarnings("WeakerAccess") // Synthetic access
StreamInfo mStreamInfo = StreamInfo.STREAM_INFO_ANY_INACTIVE;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @SuppressWarnings("WeakerAccess") // Synthetic access
@NonNull
SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @SuppressWarnings("WeakerAccess") // Synthetic access
ListenableFuture<Void> mSurfaceUpdateFuture = null;
private SurfaceRequest mSurfaceRequest;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @SuppressWarnings("WeakerAccess") // Synthetic access
VideoOutput.SourceState mSourceState = VideoOutput.SourceState.INACTIVE;
@Nullable
- private SurfaceEffectInternal mSurfaceEffect;
+ private SurfaceProcessorInternal mSurfaceProcessor;
@Nullable
- private SurfaceEffectNode mNode;
+ private SurfaceProcessorNode mNode;
@Nullable
private VideoEncoderInfo mVideoEncoderInfo;
@@ -217,7 +217,6 @@
* has been attached to a camera.
*
* @return The rotation of the intended target.
- *
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@@ -329,9 +328,9 @@
}
/**
- * Sets a {@link SurfaceEffectInternal}.
+ * Sets a {@link SurfaceProcessorInternal}.
*
- * <p>The effect is used to setup post-processing pipeline.
+ * <p>The processor is used to setup post-processing pipeline.
*
* <p>Note: the value will only be used when VideoCapture is bound. Calling this method after
* VideoCapture is bound takes no effect until VideoCapture is rebound.
@@ -339,8 +338,8 @@
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
- public void setEffect(@Nullable SurfaceEffectInternal surfaceEffect) {
- mSurfaceEffect = surfaceEffect;
+ public void setProcessor(@Nullable SurfaceProcessorInternal surfaceProcessor) {
+ mSurfaceProcessor = surfaceProcessor;
}
/**
@@ -502,7 +501,7 @@
VideoCapabilities.from(camera.getCameraInfo()), timebase, mediaSpec,
resolution, targetFpsRange));
SettableSurface cameraSurface = new SettableSurface(
- SurfaceEffect.VIDEO_CAPTURE,
+ SurfaceProcessor.VIDEO_CAPTURE,
resolution,
ImageFormat.PRIVATE,
getSensorToBufferTransformMatrix(),
@@ -697,19 +696,19 @@
}
@Nullable
- private SurfaceEffectNode createNodeIfNeeded() {
- if (mSurfaceEffect != null || HAS_PREVIEW_DELAY_QUIRK || HAS_IMAGE_CAPTURE_QUIRK) {
+ private SurfaceProcessorNode createNodeIfNeeded() {
+ if (mSurfaceProcessor != null || HAS_PREVIEW_DELAY_QUIRK || HAS_IMAGE_CAPTURE_QUIRK) {
Logger.d(TAG, "SurfaceEffect is enabled.");
- return new SurfaceEffectNode(requireNonNull(getCamera()),
+ return new SurfaceProcessorNode(requireNonNull(getCamera()),
APPLY_CROP_ROTATE_AND_MIRRORING,
- mSurfaceEffect != null ? mSurfaceEffect : new DefaultSurfaceEffect());
+ mSurfaceProcessor != null ? mSurfaceProcessor : new DefaultSurfaceProcessor());
}
return null;
}
@VisibleForTesting
@Nullable
- SurfaceEffectNode getNode() {
+ SurfaceProcessorNode getNode() {
return mNode;
}
@@ -1002,7 +1001,7 @@
* by the {@link QualitySelector} in VideoOutput.
*
* @throws IllegalArgumentException if not able to find a resolution by the QualitySelector
- * in VideoOutput.
+ * in VideoOutput.
*/
private void updateSupportedResolutionsByQuality(@NonNull CameraInfoInternal cameraInfo,
@NonNull UseCaseConfig.Builder<?, ?, ?> builder) throws IllegalArgumentException {
@@ -1088,9 +1087,9 @@
* will never return a {@code null} value. The observable could contain exact {@code null}
* value.
*
- * @param observable the observable
+ * @param observable the observable
* @param valueIfMissing if the observable doesn't contain value.
- * @param <T> the value type
+ * @param <T> the value type
* @return the snapshot value of the given {@link Observable}.
*/
@Nullable
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
index a7940f6..a1c7f27 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
@@ -18,12 +18,12 @@
import android.os.Build
import com.google.common.truth.Truth.assertThat
+import java.io.File
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index a2d7fc1..af83bd4 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -40,7 +40,7 @@
import androidx.camera.core.impl.utils.TransformUtils.rotateSize
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.core.processing.SurfaceEffectInternal
+import androidx.camera.core.processing.SurfaceProcessorInternal
import androidx.camera.testing.CamcorderProfileUtil
import androidx.camera.testing.CamcorderProfileUtil.PROFILE_1080P
import androidx.camera.testing.CamcorderProfileUtil.PROFILE_2160P
@@ -58,7 +58,7 @@
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
import androidx.camera.testing.fakes.FakeCameraFactory
import androidx.camera.testing.fakes.FakeCameraInfoInternal
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
import androidx.camera.video.StreamInfo.StreamState
import androidx.camera.video.impl.VideoCaptureConfig
import androidx.camera.video.internal.encoder.FakeVideoEncoderInfo
@@ -142,40 +142,48 @@
}
@Test
- fun enableEffect_sensorRotationIs0AndSetTargetRotation_sendCorrectResolution() {
+ fun enableProcessor_sensorRotationIs0AndSetTargetRotation_sendCorrectResolution() {
testSetRotationWillSendCorrectResolution(
sensorRotation = 0,
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ )
)
}
@Test
- fun enableEffect_sensorRotationIs90AndSetTargetRotation_sendCorrectResolution() {
+ fun enableProcessor_sensorRotationIs90AndSetTargetRotation_sendCorrectResolution() {
testSetRotationWillSendCorrectResolution(
sensorRotation = 90,
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ )
)
}
@Test
- fun enableEffect_sensorRotationIs180AndSetTargetRotation_sendCorrectResolution() {
+ fun enableProcessor_sensorRotationIs180AndSetTargetRotation_sendCorrectResolution() {
testSetRotationWillSendCorrectResolution(
sensorRotation = 180,
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ )
)
}
@Test
- fun enableEffect_sensorRotationIs270AndSetTargetRotation_sendCorrectResolution() {
+ fun enableProcessor_sensorRotationIs270AndSetTargetRotation_sendCorrectResolution() {
testSetRotationWillSendCorrectResolution(
sensorRotation = 270,
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ )
)
}
private fun testSetRotationWillSendCorrectResolution(
sensorRotation: Int = 0,
- effect: SurfaceEffectInternal? = null
+ processor: SurfaceProcessorInternal? = null
) {
setupCamera(sensorRotation = sensorRotation)
createCameraUseCaseAdapter()
@@ -196,14 +204,14 @@
surfaceRequest = request
})
val videoCapture = createVideoCapture(videoOutput)
- effect?.let { videoCapture.setEffect(it) }
+ processor?.let { videoCapture.setProcessor(it) }
videoCapture.targetRotation = targetRotation
// Act.
addAndAttachUseCases(videoCapture)
// Assert.
- val expectedResolution = if (effect != null) {
+ val expectedResolution = if (processor != null) {
rotateSize(RESOLUTION_720P, cameraInfo.getSensorRotationDegrees(targetRotation))
} else {
RESOLUTION_720P
@@ -227,25 +235,29 @@
}
@Test
- fun addUseCasesWithSurfaceEffect_cameraIsUptime_requestIsUptime() {
+ fun addUseCasesWithSurfaceProcessor_cameraIsUptime_requestIsUptime() {
testTimebase(
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor()),
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ ),
cameraTimebase = Timebase.UPTIME,
expectedTimebase = Timebase.UPTIME
)
}
@Test
- fun addUseCasesWithSurfaceEffect_cameraIsRealtime_requestIsRealtime() {
+ fun addUseCasesWithSurfaceProcessor_cameraIsRealtime_requestIsRealtime() {
testTimebase(
- effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor()),
+ processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ ),
cameraTimebase = Timebase.REALTIME,
expectedTimebase = Timebase.REALTIME
)
}
private fun testTimebase(
- effect: SurfaceEffectInternal? = null,
+ processor: SurfaceProcessorInternal? = null,
cameraTimebase: Timebase,
expectedTimebase: Timebase
) {
@@ -260,7 +272,7 @@
val videoCapture = VideoCapture.Builder(videoOutput)
.setSessionOptionUnpacker { _, _ -> }
.build()
- effect?.let { videoCapture.setEffect(it) }
+ processor?.let { videoCapture.setProcessor(it) }
// Act.
addAndAttachUseCases(videoCapture)
@@ -551,7 +563,10 @@
// Arrange.
setupCamera()
createCameraUseCaseAdapter()
- val effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor(), false)
+ val processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor(),
+ false
+ )
var appSurfaceReadyToRelease = false
val videoOutput = createVideoOutput(surfaceRequestListener = { surfaceRequest, _ ->
surfaceRequest.provideSurface(
@@ -564,30 +579,30 @@
val videoCapture = createVideoCapture(videoOutput)
// Act: bind and provide Surface.
- videoCapture.setEffect(effect)
+ videoCapture.setProcessor(processor)
addAndAttachUseCases(videoCapture)
// Assert: surfaceOutput received.
- assertThat(effect.surfaceOutput).isNotNull()
- assertThat(effect.isReleased).isFalse()
- assertThat(effect.isOutputSurfaceRequestedToClose).isFalse()
- assertThat(effect.isInputSurfaceReleased).isFalse()
+ assertThat(processor.surfaceOutput).isNotNull()
+ assertThat(processor.isReleased).isFalse()
+ assertThat(processor.isOutputSurfaceRequestedToClose).isFalse()
+ assertThat(processor.isInputSurfaceReleased).isFalse()
assertThat(appSurfaceReadyToRelease).isFalse()
- // effect surface is provided to camera.
+ // processor surface is provided to camera.
assertThat(videoCapture.sessionConfig.surfaces[0].surface.get())
- .isEqualTo(effect.inputSurface)
+ .isEqualTo(processor.inputSurface)
// Act: unbind.
detachAndRemoveUseCases(videoCapture)
- // Assert: effect and effect surface is released.
- assertThat(effect.isReleased).isTrue()
- assertThat(effect.isOutputSurfaceRequestedToClose).isTrue()
- assertThat(effect.isInputSurfaceReleased).isTrue()
+ // Assert: processor and processor surface is released.
+ assertThat(processor.isReleased).isTrue()
+ assertThat(processor.isOutputSurfaceRequestedToClose).isTrue()
+ assertThat(processor.isInputSurfaceReleased).isTrue()
assertThat(appSurfaceReadyToRelease).isFalse()
// Act: close SurfaceOutput
- effect.surfaceOutput!!.close()
+ processor.surfaceOutput!!.close()
shadowOf(Looper.getMainLooper()).idle()
assertThat(appSurfaceReadyToRelease).isTrue()
}
@@ -680,8 +695,10 @@
val videoCapture = createVideoCapture(
videoOutput, videoEncoderInfoFinder = { videoEncoderInfo }
)
- val effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
- videoCapture.setEffect(effect)
+ val processor = FakeSurfaceProcessorInternal(
+ CameraXExecutors.mainThreadExecutor()
+ )
+ videoCapture.setProcessor(processor)
videoCapture.setViewPortCropRect(cropRect)
// Act.
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
index 8fa89c7..33cc6eb 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
@@ -21,13 +21,13 @@
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_UNKNOWN
import com.google.common.truth.Truth.assertThat
+import java.io.File
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
private const val INVALID_FILE_PATH = "/invalid/file/path"
private val TEST_OUTPUT_OPTION = FileOutputOptions.Builder(File(INVALID_FILE_PATH)).build()
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index 43a0597..8abbab7 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -28,7 +28,7 @@
import androidx.camera.core.CameraXConfig
import androidx.camera.core.EffectBundle
import androidx.camera.core.ImageCapture
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.CameraPipeConfigTestRule
@@ -37,7 +37,7 @@
import androidx.camera.testing.CoreAppTestUtil
import androidx.camera.testing.fakes.FakeActivity
import androidx.camera.testing.fakes.FakeLifecycleOwner
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
@@ -131,13 +131,17 @@
instrumentation.runOnMainSync {
controller!!.setEffectBundle(
EffectBundle.Builder(mainThreadExecutor())
- .addEffect(PREVIEW, FakeSurfaceEffect(mainThreadExecutor()))
+ .addEffect(PREVIEW,
+ FakeSurfaceProcessor(
+ mainThreadExecutor()
+ )
+ )
.build()
)
}
// Assert: preview has effect
- assertThat(controller!!.mPreview.effect).isNotNull()
+ assertThat(controller!!.mPreview.processor).isNotNull()
// Act: clear the EffectBundle
instrumentation.runOnMainSync {
@@ -145,7 +149,7 @@
}
// Assert: preview no longer has the effect.
- assertThat(controller!!.mPreview.effect).isNull()
+ assertThat(controller!!.mPreview.processor).isNull()
}
@Test
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 1c8e0e8..5de58b5 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -15,7 +15,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" />
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index b9f5851..773eb0e 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -41,6 +41,7 @@
import android.hardware.display.DisplayManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -154,12 +155,25 @@
*/
public class CameraXActivity extends AppCompatActivity {
private static final String TAG = "CameraXActivity";
- private static final String[] REQUIRED_PERMISSIONS =
- new String[]{
+ private static final String[] REQUIRED_PERMISSIONS;
+
+ static {
+
+ //WRITE_EXTERNAL_STORAGE permission is not needed for SDK 33 or later to store media
+ if (Build.VERSION.SDK_INT >= 33) {
+ REQUIRED_PERMISSIONS = new String[]{
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO
+ };
+ } else {
+ REQUIRED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
+ }
+ }
+
// Possible values for this intent key: "backward" or "forward".
private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
// Possible values for this intent key: "switch_test_case", "preview_test_case" or
@@ -632,7 +646,7 @@
} else if (outputOptions instanceof FileOutputOptions) {
videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
MediaScannerConnection.scanFile(this,
- new String[] { videoFilePath }, null,
+ new String[]{videoFilePath}, null,
(path, uri1) -> {
Log.i(TAG, "Scanned " + path + " -> uri= " + uri1);
updateVideoSavedSessionData(uri1);
@@ -1395,8 +1409,8 @@
for (String permission : REQUIRED_PERMISSIONS) {
if (!Objects.requireNonNull(result.get(permission))) {
Toast.makeText(getApplicationContext(),
- "Camera permission denied.",
- Toast.LENGTH_SHORT)
+ "Camera permission denied.",
+ Toast.LENGTH_SHORT)
.show();
finish();
return;
@@ -1581,10 +1595,10 @@
cameraInfo.getZoomState().removeObservers(this);
cameraInfo.getZoomState().observe(this,
state -> {
- String str = String.format("%.2fx", state.getZoomRatio());
- mZoomRatioLabel.setText(str);
- mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
- });
+ String str = String.format("%.2fx", state.getZoomRatio());
+ mZoomRatioLabel.setText(str);
+ mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
+ });
}
private boolean is2XZoomSupported() {
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index 5519596..f949eb5 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -134,7 +134,7 @@
fun enableEffect_effectIsEnabled() {
// Arrange: launch app and verify effect is inactive.
fragment.assertPreviewIsStreaming()
- assertThat(fragment.mSurfaceEffect.isSurfaceRequestedAndProvided()).isFalse()
+ assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isFalse()
// Act: turn on effect.
val effectToggleId = "androidx.camera.integration.view:id/effect_toggle"
@@ -142,7 +142,7 @@
instrumentation.waitForIdleSync()
// Assert: verify that effect is active.
- assertThat(fragment.mSurfaceEffect.isSurfaceRequestedAndProvided()).isTrue()
+ assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isTrue()
}
@Test
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index d558b58..97a9bde 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -60,7 +60,7 @@
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -126,7 +126,7 @@
private ImageAnalysis.Analyzer mWrappedAnalyzer;
@VisibleForTesting
- ToneMappingSurfaceEffect mSurfaceEffect;
+ ToneMappingSurfaceProcessor mSurfaceProcessor;
private final ImageAnalysis.Analyzer mAnalyzer = image -> {
byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()];
@@ -182,7 +182,7 @@
});
// Set up post-processing effects.
- mSurfaceEffect = new ToneMappingSurfaceEffect();
+ mSurfaceProcessor = new ToneMappingSurfaceProcessor();
mEffectToggle = view.findViewById(R.id.effect_toggle);
mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled());
onEffectsToggled();
@@ -352,15 +352,15 @@
mExecutorService.shutdown();
}
mRotationProvider.removeListener(mRotationListener);
- mSurfaceEffect.release();
+ mSurfaceProcessor.release();
}
private void onEffectsToggled() {
if (mEffectToggle.isChecked()) {
mCameraController.setEffectBundle(new EffectBundle.Builder(mainThreadExecutor())
- .addEffect(SurfaceEffect.PREVIEW, mSurfaceEffect)
+ .addEffect(SurfaceProcessor.PREVIEW, mSurfaceProcessor)
.build());
- } else if (mSurfaceEffect != null) {
+ } else if (mSurfaceProcessor != null) {
mCameraController.setEffectBundle(null);
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
similarity index 96%
rename from camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
rename to camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
index 2ed13ae..e70fb20 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
@@ -22,8 +22,8 @@
import android.os.Looper
import android.view.Surface
import androidx.annotation.VisibleForTesting
-import androidx.camera.core.SurfaceEffect
import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.utils.Threads.checkMainThread
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -31,11 +31,11 @@
import androidx.camera.core.processing.ShaderProvider
/**
- * A effect that applies tone mapping on camera output.
+ * A processor that applies tone mapping on camera output.
*
* <p>The thread safety is guaranteed by using the main thread.
*/
-class ToneMappingSurfaceEffect : SurfaceEffect, OnFrameAvailableListener {
+class ToneMappingSurfaceProcessor : SurfaceProcessor, OnFrameAvailableListener {
companion object {
// A fragment shader that applies a yellow hue.
diff --git a/car/app/app-automotive/lint-baseline.xml b/car/app/app-automotive/lint-baseline.xml
new file mode 100644
index 0000000..5840334
--- /dev/null
+++ b/car/app/app-automotive/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type com.google.common.collect.ImmutableMap<java.util.Set<androidx.car.app.hardware.common.CarZone>,java.util.Set<java.lang.Integer>>; expected int"
+ errorLine1=" @HvacFanDirection"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/car/app/hardware/common/CarPropertyProfile.java"/>
+ </issue>
+
+</issues>
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java b/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
index da88bc2..f22d30f 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
@@ -24,10 +24,12 @@
import android.media.AudioRecord;
import android.media.MediaRecorder;
+import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import androidx.annotation.RestrictTo;
import androidx.car.app.CarContext;
+import androidx.car.app.annotations.CarProtocol;
/**
* A {@link CarAudioRecord} for automotive OS.
@@ -35,6 +37,8 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
+@Keep
+@CarProtocol
public class AutomotiveCarAudioRecord extends CarAudioRecord {
/**
* Only used for Automotive, as the car microphone is the device microphone.
diff --git a/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java b/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
index 38a98ba..b55a5c2 100644
--- a/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
+++ b/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
@@ -23,11 +23,13 @@
import android.util.Log;
+import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import androidx.annotation.RestrictTo;
import androidx.car.app.CarContext;
+import androidx.car.app.annotations.CarProtocol;
import java.io.IOException;
import java.io.InputStream;
@@ -38,6 +40,8 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
+@Keep
+@CarProtocol
public class ProjectedCarAudioRecord extends CarAudioRecord {
@Nullable
private InputStream mInputStream;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
index c8d4a62..a054acf 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
@@ -35,6 +35,7 @@
public final class ShowcaseService extends CarAppService {
public static final String SHARED_PREF_KEY = "ShowcasePrefs";
public static final String PRE_SEED_KEY = "PreSeed";
+ public static final String LOADING_KEY = "LoadingKey";
// Intent actions for notification actions in car and phone
public static final String INTENT_ACTION_NAVIGATE =
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
new file mode 100644
index 0000000..0d321708
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.Toggle;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.car.app.sample.showcase.common.ShowcaseSession;
+import androidx.car.app.sample.showcase.common.screens.settings.CarHardwareDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.settings.ContentLimitsDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.settings.LatestFeatures;
+import androidx.car.app.sample.showcase.common.screens.settings.ParkedVsDrivingDemoScreen;
+
+/** A screen demonstrating selectable lists. */
+public final class SettingsScreen extends Screen {
+
+ private boolean mLoadingToggleState;
+
+ @NonNull
+ private final ShowcaseSession mShowcaseSession;
+
+ public SettingsScreen(@NonNull CarContext carContext,
+ @NonNull ShowcaseSession showcaseSession) {
+ super(carContext);
+ mShowcaseSession = showcaseSession;
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ Toggle mLoadingToggle = new Toggle.Builder((checked) -> {
+ if (checked) {
+ makeCarToast(R.string.loading_toggle_enabled);
+ setLoadingKeyValue(true);
+ } else {
+ makeCarToast(R.string.loading_toggle_disabled);
+ setLoadingKeyValue(false);
+ }
+ mLoadingToggleState = !mLoadingToggleState;
+ }).setChecked(mLoadingToggleState).build();
+
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(new LatestFeatures(getCarContext()),
+ R.string.latest_feature_title));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.loading_demo_title, mLoadingToggle));
+
+ listBuilder.addItem(buildRowForTemplate(new ContentLimitsDemoScreen(getCarContext()),
+ R.string.content_limits_demo_title));
+
+ listBuilder.addItem(buildRowForTemplate(new ParkedVsDrivingDemoScreen(getCarContext()),
+ R.string.parking_vs_driving_demo_title));
+
+ listBuilder.addItem(buildRowForTemplate(new CarHardwareDemoScreen(getCarContext(),
+ mShowcaseSession), R.string.car_hardware_demo_title));
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.settings_action_title) + " ("
+ + getCarContext().getString(R.string.cal_api_level_prefix,
+ getCarContext().getCarAppApiLevel()) + ")")
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(Screen screen, int title) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setOnClickListener(() -> getScreenManager().push(screen))
+ .setBrowsable(true)
+ .build();
+ }
+
+ private Row buildRowForTemplate(int title, Toggle toggle) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setToggle(toggle)
+ .build();
+ }
+
+ private void makeCarToast(int toastText) {
+ CarToast.makeText(getCarContext(), toastText,
+ LENGTH_LONG).show();
+ }
+
+ private void setLoadingKeyValue(boolean val) {
+ getCarContext()
+ .getSharedPreferences(
+ ShowcaseService.SHARED_PREF_KEY,
+ Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(
+ ShowcaseService.LOADING_KEY, val)
+ .apply();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
new file mode 100644
index 0000000..76ab41e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.GridTemplateMenuDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.ListTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.MessageTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.PaneTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.SearchTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.SignInTemplateDemoScreen;
+
+/** A screen demonstrating different template layouts. */
+public final class TemplateLayoutsDemoScreen extends Screen {
+
+ public TemplateLayoutsDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(new ListTemplateDemoScreen(getCarContext()),
+ R.string.list_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new GridTemplateMenuDemoScreen(getCarContext()),
+ R.string.grid_template_menu_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new MessageTemplateDemoScreen(getCarContext()),
+ R.string.msg_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new PaneTemplateDemoScreen(getCarContext()),
+ R.string.pane_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new SearchTemplateDemoScreen(getCarContext()),
+ R.string.search_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new SignInTemplateDemoScreen(getCarContext()),
+ R.string.sign_in_template_demo_title));
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.template_layouts_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(Screen screen, int title) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setOnClickListener(() -> getScreenManager().push(screen))
+ .setBrowsable(true)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java
new file mode 100644
index 0000000..f7b6614
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.settings;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.NavigationTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseSession;
+import androidx.car.app.sample.showcase.common.renderer.CarHardwareRenderer;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+/** Simple demo of how access car hardware information. */
+public final class CarHardwareDemoScreen extends Screen {
+
+ // Package private for inner class reference
+ final CarHardwareRenderer mCarHardwareRenderer;
+
+ public CarHardwareDemoScreen(@NonNull CarContext carContext,
+ @NonNull ShowcaseSession showcaseSession) {
+ super(carContext);
+ mCarHardwareRenderer = new CarHardwareRenderer(carContext);
+ Lifecycle lifecycle = getLifecycle();
+ lifecycle.addObserver(new DefaultLifecycleObserver() {
+
+ @NonNull
+ final ShowcaseSession mShowcaseSession = showcaseSession;
+
+ @Override
+ public void onResume(@NonNull LifecycleOwner owner) {
+ // When this screen is visible set the SurfaceRenderer to show
+ // CarHardware information.
+ mShowcaseSession.overrideRenderer(mCarHardwareRenderer);
+ }
+
+ @Override
+ public void onPause(@NonNull LifecycleOwner owner) {
+ // When this screen is hidden set the SurfaceRenderer to show
+ // CarHardware information.
+ mShowcaseSession.overrideRenderer(null);
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ActionStrip actionStrip =
+ new ActionStrip.Builder()
+ // Add a Button to show the CarHardware info screen
+ .addAction(new Action.Builder()
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.info_gm_grey_24dp))
+ .build())
+ .setOnClickListener(() -> getScreenManager().push(
+ new CarHardwareInfoScreen(getCarContext())))
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.back_caps_action_title))
+ .setOnClickListener(this::finish)
+ .build())
+ .build();
+
+ return new NavigationTemplate.Builder().setActionStrip(actionStrip).build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java
new file mode 100644
index 0000000..8a14d77
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.settings;
+
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.hardware.CarHardwareManager;
+import androidx.car.app.hardware.common.CarValue;
+import androidx.car.app.hardware.common.OnCarDataAvailableListener;
+import androidx.car.app.hardware.info.CarInfo;
+import androidx.car.app.hardware.info.EnergyProfile;
+import androidx.car.app.hardware.info.Model;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Pane;
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Creates a screen that show the static information (such as model and energy profile) available
+ * via CarHardware interfaces.
+ */
+public final class CarHardwareInfoScreen extends Screen {
+ private static final String TAG = "showcase";
+
+ // Package private for inner class reference
+ boolean mHasModelPermission;
+ boolean mHasEnergyProfilePermission;
+ final Executor mCarHardwareExecutor;
+
+ /**
+ * Value fetched from CarHardwareManager containing model information.
+ *
+ * <p>It is requested asynchronously and can be {@code null} until the response is
+ * received.
+ */
+ @Nullable
+ @GuardedBy("this")
+ Model mModel;
+
+ /**
+ * Value fetched from CarHardwareManager containing what type of fuel/ports the car has.
+ *
+ * <p>It is requested asynchronously and can be {@code null} until the response is
+ * received.
+ */
+ @Nullable
+ @GuardedBy("this")
+ EnergyProfile mEnergyProfile;
+
+ OnCarDataAvailableListener<Model> mModelListener = data -> {
+ synchronized (this) {
+ Log.i(TAG, "Received model information: " + data);
+ mModel = data;
+ invalidate();
+ }
+ };
+
+ OnCarDataAvailableListener<EnergyProfile> mEnergyProfileListener = data -> {
+ synchronized (this) {
+ Log.i(TAG, "Received energy profile information: " + data);
+ mEnergyProfile = data;
+ invalidate();
+ }
+ };
+
+ public CarHardwareInfoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ mCarHardwareExecutor = ContextCompat.getMainExecutor(getCarContext());
+ Lifecycle lifecycle = getLifecycle();
+ lifecycle.addObserver(new DefaultLifecycleObserver() {
+
+ @Override
+ public void onCreate(@NonNull LifecycleOwner owner) {
+ CarHardwareManager carHardwareManager =
+ getCarContext().getCarService(CarHardwareManager.class);
+ CarInfo carInfo = carHardwareManager.getCarInfo();
+
+ // Request any single shot values.
+ synchronized (CarHardwareInfoScreen.this) {
+ mModel = null;
+
+ try {
+ carInfo.fetchModel(mCarHardwareExecutor, mModelListener);
+ mHasModelPermission = true;
+ } catch (SecurityException e) {
+ mHasModelPermission = false;
+ }
+
+ mEnergyProfile = null;
+ try {
+ carInfo.fetchEnergyProfile(mCarHardwareExecutor, mEnergyProfileListener);
+ mHasEnergyProfilePermission = true;
+ } catch (SecurityException e) {
+ mHasEnergyProfilePermission = false;
+ }
+ }
+ }
+
+ });
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ Pane.Builder paneBuilder = new Pane.Builder();
+ if (allInfoAvailable()) {
+ Row.Builder modelRowBuilder = new Row.Builder()
+ .setTitle(getCarContext().getString(R.string.model_info));
+ if (!mHasModelPermission) {
+ modelRowBuilder.addText(getCarContext().getString(R.string.no_model_permission));
+ } else {
+ StringBuilder info = new StringBuilder();
+ synchronized (CarHardwareInfoScreen.this) {
+ if (mModel.getManufacturer().getStatus() != CarValue.STATUS_SUCCESS) {
+ info.append(getCarContext().getString(R.string.manufacturer_unavailable));
+ info.append(", ");
+ } else {
+ info.append(mModel.getManufacturer().getValue());
+ info.append(", ");
+ }
+ if (mModel.getName().getStatus() != CarValue.STATUS_SUCCESS) {
+ info.append(getCarContext().getString(R.string.model_unavailable));
+ info.append(", ");
+ } else {
+ info.append(mModel.getName().getValue());
+ info.append(", ");
+ }
+ if (mModel.getYear().getStatus() != CarValue.STATUS_SUCCESS) {
+ info.append(getCarContext().getString(R.string.year_unavailable));
+ } else {
+ info.append(mModel.getYear().getValue());
+ }
+ }
+ modelRowBuilder.addText(info);
+ }
+ paneBuilder.addRow(modelRowBuilder.build());
+
+ Row.Builder energyProfileRowBuilder = new Row.Builder()
+ .setTitle(getCarContext().getString(R.string.energy_profile));
+ if (!mHasEnergyProfilePermission) {
+ energyProfileRowBuilder.addText(getCarContext()
+ .getString(R.string.no_energy_profile_permission));
+ } else {
+ StringBuilder fuelInfo = new StringBuilder();
+
+ synchronized (this) {
+ if (mEnergyProfile.getFuelTypes().getStatus() != CarValue.STATUS_SUCCESS) {
+ fuelInfo.append(getCarContext().getString(R.string.fuel_types));
+ fuelInfo.append(": ");
+ fuelInfo.append(getCarContext().getString(R.string.unavailable));
+ } else {
+ fuelInfo.append(getCarContext().getString(R.string.fuel_types));
+ fuelInfo.append(": ");
+ for (int fuelType : mEnergyProfile.getFuelTypes().getValue()) {
+ fuelInfo.append(fuelTypeAsString(fuelType));
+ fuelInfo.append(" ");
+ }
+ }
+ energyProfileRowBuilder.addText(fuelInfo);
+ StringBuilder evInfo = new StringBuilder();
+ if (mEnergyProfile.getEvConnectorTypes().getStatus()
+ != CarValue.STATUS_SUCCESS) {
+ evInfo.append(" ");
+ evInfo.append(getCarContext().getString(R.string.ev_connector_types));
+ evInfo.append(": ");
+ evInfo.append(getCarContext().getString(R.string.unavailable));
+ } else {
+ evInfo.append(getCarContext().getString(R.string.ev_connector_types));
+ evInfo.append(": ");
+ for (int connectorType : mEnergyProfile.getEvConnectorTypes().getValue()) {
+ evInfo.append(evConnectorAsString(connectorType));
+ evInfo.append(" ");
+ }
+ }
+ energyProfileRowBuilder.addText(evInfo);
+ }
+ }
+ paneBuilder.addRow(energyProfileRowBuilder.build());
+ } else {
+ paneBuilder.setLoading(true);
+ }
+ return new PaneTemplate.Builder(paneBuilder.build())
+ .setHeaderAction(Action.BACK)
+ .setTitle(getCarContext().getString(R.string.car_hardware_info))
+ .build();
+ }
+
+ private boolean allInfoAvailable() {
+ synchronized (this) {
+ if (mHasModelPermission && mModel == null) {
+ return false;
+ }
+ if (mHasEnergyProfilePermission && mEnergyProfile == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private String fuelTypeAsString(int fuelType) {
+ switch (fuelType) {
+ case EnergyProfile.FUEL_TYPE_UNLEADED:
+ return "UNLEADED";
+ case EnergyProfile.FUEL_TYPE_LEADED:
+ return "LEADED";
+ case EnergyProfile.FUEL_TYPE_DIESEL_1:
+ return "DIESEL_1";
+ case EnergyProfile.FUEL_TYPE_DIESEL_2:
+ return "DIESEL_2";
+ case EnergyProfile.FUEL_TYPE_BIODIESEL:
+ return "BIODIESEL";
+ case EnergyProfile.FUEL_TYPE_E85:
+ return "E85";
+ case EnergyProfile.FUEL_TYPE_LPG:
+ return "LPG";
+ case EnergyProfile.FUEL_TYPE_CNG:
+ return "CNG";
+ case EnergyProfile.FUEL_TYPE_LNG:
+ return "LNG";
+ case EnergyProfile.FUEL_TYPE_ELECTRIC:
+ return "ELECTRIC";
+ case EnergyProfile.FUEL_TYPE_HYDROGEN:
+ return "HYDROGEN";
+ case EnergyProfile.FUEL_TYPE_OTHER:
+ return "OTHER";
+ case EnergyProfile.FUEL_TYPE_UNKNOWN:
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private String evConnectorAsString(int evConnectorType) {
+ switch (evConnectorType) {
+ case EnergyProfile.EVCONNECTOR_TYPE_J1772:
+ return "J1772";
+ case EnergyProfile.EVCONNECTOR_TYPE_MENNEKES:
+ return "MENNEKES";
+ case EnergyProfile.EVCONNECTOR_TYPE_CHADEMO:
+ return "CHADEMO";
+ case EnergyProfile.EVCONNECTOR_TYPE_COMBO_1:
+ return "COMBO_1";
+ case EnergyProfile.EVCONNECTOR_TYPE_COMBO_2:
+ return "COMBO_2";
+ case EnergyProfile.EVCONNECTOR_TYPE_TESLA_ROADSTER:
+ return "TESLA_ROADSTER";
+ case EnergyProfile.EVCONNECTOR_TYPE_TESLA_HPWC:
+ return "TESLA_HPWC";
+ case EnergyProfile.EVCONNECTOR_TYPE_TESLA_SUPERCHARGER:
+ return "TESLA_SUPERCHARGER";
+ case EnergyProfile.EVCONNECTOR_TYPE_GBT:
+ return "GBT";
+ case EnergyProfile.EVCONNECTOR_TYPE_GBT_DC:
+ return "GBT_DC";
+ case EnergyProfile.EVCONNECTOR_TYPE_SCAME:
+ return "SCAME";
+ case EnergyProfile.EVCONNECTOR_TYPE_OTHER:
+ return "OTHER";
+ case EnergyProfile.EVCONNECTOR_TYPE_UNKNOWN:
+ default:
+ return "UNKNOWN";
+ }
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
new file mode 100644
index 0000000..8a728b4
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/**
+ * A {@link Screen} that shows examples on how to query for various content limits via the
+ * {@lnk ConstraintManager} API.
+ */
+public class ContentLimitsDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+ // Loading State parameters
+ private static final int LOADING_TIME_MILLIS = 1000;
+ private boolean mIsFinishedLoading;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mShouldLoadScreens;
+
+ public ContentLimitsDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @Override
+ @SuppressWarnings({"FutureReturnValueIgnored"})
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mShouldLoadScreens =
+ getCarContext()
+ .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+ .getBoolean(ShowcaseService.LOADING_KEY, false);
+ if (mShouldLoadScreens) {
+ // Post a message that finishes loading the template after some time.
+ mHandler.postDelayed(
+ () -> {
+ mIsFinishedLoading = true;
+ invalidate();
+ },
+ LOADING_TIME_MILLIS);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+
+ if (!mIsFinishedLoading && mShouldLoadScreens) {
+ return loadingScreenTemplate(getCarContext());
+ }
+
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(R.string.list_limit,
+ ConstraintManager.CONTENT_LIMIT_TYPE_LIST));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.grid_limit,
+ ConstraintManager.CONTENT_LIMIT_TYPE_GRID));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.pane_limit,
+ ConstraintManager.CONTENT_LIMIT_TYPE_PANE));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.place_list_limit,
+ ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.route_list_limit,
+ ConstraintManager.CONTENT_LIMIT_TYPE_ROUTE_LIST));
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.content_limits))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(int title, int contentLimitType) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .addText(Integer.toString(getCarContext()
+ .getCarService(ConstraintManager.class)
+ .getContentLimit(contentLimitType)))
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java
new file mode 100644
index 0000000..26b7a6e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.settings;
+
+
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A screen that demonstrates the message template. */
+public class LatestFeatures extends Screen implements DefaultLifecycleObserver {
+
+ private static final int LOADING_TIME_MILLIS = 1000;
+ private boolean mIsFinishedLoading;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mShouldLoadScreens;
+
+ public LatestFeatures(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @Override
+ @SuppressWarnings({"FutureReturnValueIgnored"})
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mShouldLoadScreens =
+ getCarContext()
+ .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+ .getBoolean(ShowcaseService.LOADING_KEY, false);
+ if (mShouldLoadScreens) {
+ // Post a message that finishes loading the template after some time.
+ mHandler.postDelayed(
+ () -> {
+ mIsFinishedLoading = true;
+ invalidate();
+ },
+ LOADING_TIME_MILLIS);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ if (!mIsFinishedLoading && mShouldLoadScreens) {
+ return loadingScreenTemplate(getCarContext());
+ } else {
+ return new MessageTemplate.Builder(
+ getCarContext().getString(R.string.latest_feature_details))
+ .setTitle(getCarContext().getString(R.string.latest_feature_title))
+ .setHeaderAction(Action.BACK)
+ .build();
+ }
+
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java
new file mode 100644
index 0000000..f6fae20
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+
+/** A class that provides a sample template for a loading screen */
+public abstract class LoadingScreen {
+
+ private LoadingScreen() {
+ }
+
+ /**
+ * Returns a sample template to be used for loading a screen
+ */
+ @NonNull
+ public static Template loadingScreenTemplate(@NonNull CarContext carContext) {
+ return new MessageTemplate.Builder(
+ carContext.getString(R.string.loading_screen))
+ .setLoading(true)
+ .setHeaderAction(BACK)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
new file mode 100644
index 0000000..ba198e9
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A screen demonstrating selectable lists. */
+public final class ParkedVsDrivingDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+ // Adding loading state parameters
+ private static final int LOADING_TIME_MILLIS = 1000;
+ private boolean mIsFinishedLoading;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mShouldLoadScreens;
+
+ public ParkedVsDrivingDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @Override
+ @SuppressWarnings({"FutureReturnValueIgnored"})
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mShouldLoadScreens =
+ getCarContext()
+ .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+ .getBoolean(ShowcaseService.LOADING_KEY, false);
+ if (mShouldLoadScreens) {
+ // Post a message that finishes loading the template after some time.
+ mHandler.postDelayed(
+ () -> {
+ mIsFinishedLoading = true;
+ invalidate();
+ },
+ LOADING_TIME_MILLIS);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+
+ if (!mIsFinishedLoading && mShouldLoadScreens) {
+ return loadingScreenTemplate(getCarContext());
+ }
+
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+
+ listBuilder.addItem(
+ new Row.Builder()
+ .setOnClickListener(
+ ParkedOnlyOnClickListener.create(() -> onClick(
+ getCarContext().getString(R.string.parked_toast_msg))))
+ .setTitle(getCarContext().getString(R.string.parked_only_title))
+ .addText(getCarContext().getString(R.string.parked_only_text))
+ .build());
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.parking_vs_driving_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private void onClick(String text) {
+ CarToast.makeText(getCarContext(), text, LENGTH_LONG).show();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
new file mode 100644
index 0000000..930f1b3
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.misc.NotificationDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates.GridTemplateDemoScreen;
+import androidx.lifecycle.DefaultLifecycleObserver;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class GridTemplateMenuDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+ public GridTemplateMenuDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(new GridTemplateDemoScreen(getCarContext()),
+ R.string.grid_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new NotificationDemoScreen(getCarContext()),
+ R.string.notification_template_demo_title));
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.grid_template_menu_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(Screen screen, int title) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setOnClickListener(() -> getScreenManager().push(screen))
+ .setBrowsable(true)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
new file mode 100644
index 0000000..1888b87
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.ContentProviderIconsDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.RadioButtonListDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.TextAndIconsDemosScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.ToggleButtonListDemoScreen;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class ListTemplateDemoScreen extends Screen {
+
+ public ListTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+ listBuilder.addItem(buildRowForTemplate(new RadioButtonListDemoScreen(getCarContext()),
+ R.string.radio_button_list_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new ToggleButtonListDemoScreen(getCarContext()),
+ R.string.toggle_button_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new TextAndIconsDemosScreen(getCarContext()),
+ R.string.text_icons_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new ContentProviderIconsDemoScreen(getCarContext()),
+ R.string.content_provider_icons_demo_title));
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.list_template_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(Screen screen, int title) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setOnClickListener(() -> getScreenManager().push(screen))
+ .setBrowsable(true)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
new file mode 100644
index 0000000..32aaa1a
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.LongMessageTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.ShortMessageTemplateDemoScreen;
+import androidx.lifecycle.DefaultLifecycleObserver;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class MessageTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+ public MessageTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(new ShortMessageTemplateDemoScreen(getCarContext()),
+ R.string.short_msg_template_demo_title));
+ listBuilder.addItem(buildRowForTemplate(new LongMessageTemplateDemoScreen(getCarContext()),
+ R.string.long_msg_template_demo_title));
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ private Row buildRowForTemplate(Screen screen, int title) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setOnClickListener(() -> getScreenManager().push(screen))
+ .setBrowsable(true)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java
new file mode 100644
index 0000000..baac535
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_SHORT;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Pane;
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link PaneTemplate} to display a
+ * details screen.
+ */
+public final class PaneTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+ @Nullable
+ private IconCompat mPaneImage;
+
+ @Nullable
+ private IconCompat mRowLargeIcon;
+
+ @Nullable
+ private IconCompat mCommuteIcon;
+
+ public PaneTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @Override
+ public void onCreate(@NonNull LifecycleOwner owner) {
+ Resources resources = getCarContext().getResources();
+ Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.patio);
+ mPaneImage = IconCompat.createWithBitmap(bitmap);
+ mRowLargeIcon = IconCompat.createWithResource(getCarContext(),
+ R.drawable.ic_fastfood_white_48dp);
+ mCommuteIcon = IconCompat.createWithResource(getCarContext(), R.drawable.ic_commute_24px);
+ }
+
+ private Row createRow(int index) {
+ switch (index) {
+ case 0:
+ // Row with a large image.
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(R.string.first_row_title))
+ .addText(getCarContext().getString(R.string.first_row_text))
+ .addText(getCarContext().getString(R.string.first_row_text))
+ .setImage(new CarIcon.Builder(mRowLargeIcon).build())
+ .build();
+ default:
+ return new Row.Builder()
+ .setTitle(
+ getCarContext().getString(R.string.other_row_title_prefix) + (index
+ + 1))
+ .addText(getCarContext().getString(R.string.other_row_text))
+ .addText(getCarContext().getString(R.string.other_row_text))
+ .build();
+ }
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ int listLimit = getCarContext().getCarService(ConstraintManager.class).getContentLimit(
+ ConstraintManager.CONTENT_LIMIT_TYPE_PANE);
+
+ Pane.Builder paneBuilder = new Pane.Builder();
+ for (int i = 0; i < listLimit; i++) {
+ paneBuilder.addRow(createRow(i));
+ }
+
+ // Also set a large image outside of the rows.
+ paneBuilder.setImage(new CarIcon.Builder(mPaneImage).build());
+
+ Action.Builder primaryActionBuilder = new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.search_action_title))
+ .setBackgroundColor(CarColor.BLUE)
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(R.string.search_toast_msg),
+ LENGTH_SHORT)
+ .show());
+ if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+ primaryActionBuilder.setFlags(FLAG_PRIMARY);
+ }
+
+ paneBuilder
+ .addAction(primaryActionBuilder.build())
+ .addAction(
+ new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.options_action_title))
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.options_toast_msg),
+ LENGTH_SHORT)
+ .show())
+ .build());
+
+ return new PaneTemplate.Builder(paneBuilder.build())
+ .setHeaderAction(Action.BACK)
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.commute_action_title))
+ .setIcon(
+ new CarIcon.Builder(mCommuteIcon)
+ .setTint(CarColor.BLUE)
+ .build())
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.commute_toast_msg),
+ LENGTH_SHORT)
+ .show())
+ .build())
+ .build())
+ .setTitle(getCarContext().getString(R.string.pane_template_demo_title))
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java
new file mode 100644
index 0000000..b0387e7
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.SearchTemplate;
+import androidx.car.app.model.SearchTemplate.SearchCallback;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+
+/** A screen that demonstrates the search template. */
+public class SearchTemplateDemoScreen extends Screen {
+
+ public SearchTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+ for (int i = 1; i <= 6; ++i) {
+ listBuilder.addItem(
+ new Row.Builder()
+ .setTitle(getCarContext().getString(R.string.title_prefix) + " " + i)
+ .addText(getCarContext().getString(R.string.first_line_text))
+ .addText(getCarContext().getString(R.string.second_line_text))
+ .build());
+ }
+
+ SearchCallback searchListener =
+ new SearchCallback() {
+ @Override
+ public void onSearchTextChanged(@NonNull String searchText) {
+ }
+
+ @Override
+ public void onSearchSubmitted(@NonNull String searchText) {
+ CarToast.makeText(
+ getCarContext(),
+ "Searched for " + searchText,
+ LENGTH_LONG)
+ .show();
+ }
+ };
+
+ ActionStrip actionStrip = new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.settings_action_title))
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.settings_toast_msg),
+ LENGTH_LONG)
+ .show())
+ .build())
+ .build();
+
+ return new SearchTemplate.Builder(searchListener)
+ .setSearchHint(getCarContext().getString(R.string.search_hint))
+ .setHeaderAction(Action.BACK)
+ .setShowKeyboardByDefault(false)
+ .setItemList(listBuilder.build())
+ .setActionStrip(actionStrip)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java
new file mode 100644
index 0000000..e552e9c
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+
+import android.graphics.Color;
+import android.net.Uri;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.InputCallback;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.signin.InputSignInMethod;
+import androidx.car.app.model.signin.PinSignInMethod;
+import androidx.car.app.model.signin.ProviderSignInMethod;
+import androidx.car.app.model.signin.QRCodeSignInMethod;
+import androidx.car.app.model.signin.SignInTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.common.Utils;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.LongMessageTemplateDemoScreen;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen that demonstrates the sign-in template. */
+public class SignInTemplateDemoScreen extends Screen {
+ private static final String EMAIL_REGEXP = "^(.+)@(.+)$";
+ private static final String EXPECTED_PASSWORD = "password";
+ private static final int MIN_USERNAME_LENGTH = 5;
+ private final CharSequence mAdditionalText;
+ private final Action mProviderSignInAction;
+ private final Action mPinSignInAction;
+ private final Action mQRCodeSignInAction;
+ // package private to avoid synthetic accessor
+ State mState = State.USERNAME;
+ String mLastErrorMessage; // last displayed error message
+ String mErrorMessage;
+
+ @Nullable
+ String mUsername;
+
+ public SignInTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+
+ // Handle back pressed events manually, as we use them to navigate between templates within
+ // the same screen.
+ OnBackPressedCallback callback = new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ mErrorMessage = "";
+ if (mState == State.USERNAME || mState == State.SIGNED_IN) {
+ getScreenManager().pop();
+ } else {
+ mState = State.USERNAME;
+ invalidate();
+ }
+ }
+ };
+ carContext.getOnBackPressedDispatcher().addCallback(this, callback);
+
+ mAdditionalText = Utils.clickable(getCarContext().getString(R.string.additional_text), 18,
+ 16,
+ () -> getScreenManager().push(new LongMessageTemplateDemoScreen(getCarContext())));
+
+ mProviderSignInAction = new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.google_sign_in))
+ .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+ mState = State.PROVIDER;
+ invalidate();
+ }))
+ .build();
+
+ mPinSignInAction = new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.use_pin))
+ .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+ mState = State.PIN;
+ invalidate();
+ }))
+ .build();
+
+ mQRCodeSignInAction = new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.qr_code))
+ .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+ mState = State.QR_CODE;
+ invalidate();
+ }))
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
+ return new MessageTemplate.Builder(
+ getCarContext().getString(R.string.sign_in_template_not_supported_text))
+ .setTitle(getCarContext().getString(
+ R.string.sign_in_template_not_supported_title))
+ .setHeaderAction(Action.BACK)
+ .build();
+ }
+ switch (mState) {
+ case USERNAME:
+ return getUsernameSignInTemplate();
+ case PASSWORD:
+ return getPasswordSignInTemplate();
+ case PIN:
+ return getPinSignInTemplate();
+ case PROVIDER:
+ return getProviderSignInTemplate();
+ case QR_CODE:
+ return getQRCodeSignInTemplate();
+ case SIGNED_IN:
+ return getSignInCompletedMessageTemplate();
+ }
+ throw new IllegalStateException("Invalid state: " + mState);
+ }
+
+ private Template getUsernameSignInTemplate() {
+ InputCallback listener = new InputCallback() {
+ @Override
+ public void onInputSubmitted(@NonNull String text) {
+ if (mState == State.USERNAME) {
+ mUsername = text;
+ submitUsername();
+ }
+ }
+
+ @Override
+ public void onInputTextChanged(@NonNull String text) {
+ // This callback demonstrates how to use handle the text changed event.
+ // In this case, we check that the user name doesn't exceed a certain length.
+ if (mState == State.USERNAME) {
+ mUsername = text;
+ mErrorMessage = validateUsername();
+
+ // Invalidate the template (and hence possibly update the error message) only
+ // if clearing up the error string, or if the error is changing.
+ if (!mLastErrorMessage.isEmpty()
+ && (mErrorMessage.isEmpty()
+ || !mLastErrorMessage.equals(mErrorMessage))) {
+ invalidate();
+ }
+ }
+ }
+ };
+
+ InputSignInMethod.Builder builder = new InputSignInMethod.Builder(listener)
+ .setHint(getCarContext().getString(R.string.email_hint))
+ .setKeyboardType(InputSignInMethod.KEYBOARD_EMAIL);
+ if (mErrorMessage != null) {
+ builder.setErrorMessage(mErrorMessage);
+ mLastErrorMessage = mErrorMessage;
+ }
+ if (mUsername != null) {
+ builder.setDefaultValue(mUsername);
+ }
+ InputSignInMethod signInMethod = builder.build();
+
+ return new SignInTemplate.Builder(signInMethod)
+ .addAction(mProviderSignInAction)
+ .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+ ? mQRCodeSignInAction : mPinSignInAction)
+ .setTitle(getCarContext().getString(R.string.sign_in_title))
+ .setInstructions(getCarContext().getString(R.string.sign_in_instructions))
+ .setHeaderAction(Action.BACK)
+ .setAdditionalText(mAdditionalText)
+ .build();
+ }
+
+ /**
+ * Validates the currently entered user name and returns an error message string if invalid,
+ * or an empty string otherwise.
+ */
+ String validateUsername() {
+ if (mUsername == null || mUsername.length() < MIN_USERNAME_LENGTH) {
+ return getCarContext().getString(R.string.invalid_length_error_msg,
+ Integer.toString(MIN_USERNAME_LENGTH));
+ } else if (!mUsername.matches(EMAIL_REGEXP)) {
+ return getCarContext().getString(R.string.invalid_email_error_msg);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Moves to the password screen if the user name currently entered is valid, or displays
+ * an error message otherwise.
+ */
+ void submitUsername() {
+ mErrorMessage = validateUsername();
+
+ boolean isError = !mErrorMessage.isEmpty();
+ if (!isError) {
+ // If there's no error, go to the password screen.
+ mState = State.PASSWORD;
+ }
+
+ // Invalidate the template so that we either display an error, or go to the password screen.
+ invalidate();
+ }
+
+ private Template getPasswordSignInTemplate() {
+ InputCallback callback = new InputCallback() {
+ @Override
+ public void onInputSubmitted(@NonNull String text) {
+ // Mocked password validation
+ if (!EXPECTED_PASSWORD.equals(text)) {
+ mErrorMessage = getCarContext().getString(R.string.invalid_password_error_msg);
+ } else {
+ mErrorMessage = "";
+ mState = State.SIGNED_IN;
+ }
+ invalidate();
+ }
+ };
+ InputSignInMethod.Builder builder = new InputSignInMethod.Builder(callback)
+ .setHint(getCarContext().getString(R.string.password_hint))
+ .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD);
+ if (mErrorMessage != null) {
+ builder.setErrorMessage(mErrorMessage);
+ }
+ InputSignInMethod signInMethod = builder.build();
+
+ return new SignInTemplate.Builder(signInMethod)
+ .addAction(mProviderSignInAction)
+ .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+ ? mQRCodeSignInAction : mPinSignInAction)
+ .setTitle(getCarContext().getString(R.string.sign_in_title))
+ .setInstructions(
+ getCarContext().getString(R.string.password_sign_in_instruction_prefix)
+ + ": " + mUsername)
+ .setHeaderAction(Action.BACK)
+ .setAdditionalText(mAdditionalText)
+ .build();
+ }
+
+ private Template getPinSignInTemplate() {
+ PinSignInMethod pinSignInMethod = new PinSignInMethod("123456789ABC");
+ return new SignInTemplate.Builder(pinSignInMethod)
+ .setTitle(getCarContext().getString(R.string.sign_in_title))
+ .setInstructions(getCarContext().getString(R.string.pin_sign_in_instruction))
+ .setHeaderAction(Action.BACK)
+ .setAdditionalText(mAdditionalText)
+ .build();
+ }
+
+ private Template getQRCodeSignInTemplate() {
+ QRCodeSignInMethod qrCodeSignInMethod = new QRCodeSignInMethod(Uri.parse("https://www"
+ + ".youtube.com/watch?v=dQw4w9WgXcQ"));
+ return new SignInTemplate.Builder(qrCodeSignInMethod)
+ .setTitle(getCarContext().getString(R.string.qr_code_sign_in_title))
+ .setHeaderAction(Action.BACK)
+ .setAdditionalText(mAdditionalText)
+ .addAction(mPinSignInAction)
+ .addAction(mProviderSignInAction)
+ .build();
+ }
+
+ private Template getProviderSignInTemplate() {
+ IconCompat providerIcon = IconCompat.createWithResource(getCarContext(),
+ R.drawable.ic_googleg);
+ CarColor noTint = CarColor.createCustom(Color.TRANSPARENT, Color.TRANSPARENT);
+
+ ProviderSignInMethod providerSignInMethod = new ProviderSignInMethod(
+ new Action.Builder()
+ .setTitle(Utils.colorize(
+ getCarContext().getString(R.string.sign_in_with_google_title),
+ CarColor.createCustom(Color.BLACK, Color.BLACK), 0, 19))
+ .setBackgroundColor(CarColor.createCustom(Color.WHITE, Color.WHITE))
+ .setIcon(new CarIcon.Builder(providerIcon)
+ .setTint(noTint)
+ .build())
+ .setOnClickListener(ParkedOnlyOnClickListener.create(
+ this::performSignInWithGoogleFlow)).build());
+
+ return new SignInTemplate.Builder(providerSignInMethod)
+ .setTitle(getCarContext().getString(R.string.sign_in_title))
+ .setInstructions(getCarContext().getString(R.string.provider_sign_in_instruction))
+ .setHeaderAction(Action.BACK)
+ .setAdditionalText(mAdditionalText)
+ .build();
+ }
+
+ private void performSignInWithGoogleFlow() {
+ // This is here for demonstration purposes, if the APK is not signed with a signature
+ // that has been registered for sign in with Google flow, the sign in will fail at runtime.
+
+// Bundle extras = new Bundle(1);
+// extras.putBinder(BINDER_KEY, new SignInWithGoogleActivity.OnSignInComplete() {
+// @Override
+// public void onSignInComplete(@Nullable GoogleSignInAccount account) {
+// if (account == null) {
+// CarToast.makeText(getCarContext(), "Error signing in", LENGTH_LONG).show();
+// return;
+// }
+//
+// // Use the account
+// CarToast.makeText(getCarContext(),
+// account.getGivenName() + " signed in", LENGTH_LONG).show();
+// }
+// });
+// getCarContext().startActivity(
+// new Intent()
+// .setClass(getCarContext(), SignInWithGoogleActivity.class)
+// .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+// .putExtras(extras));
+ CarToast.makeText(getCarContext(),
+ getCarContext().getString(R.string.sign_in_with_google_toast_msg),
+ LENGTH_LONG)
+ .show();
+ }
+
+ private MessageTemplate getSignInCompletedMessageTemplate() {
+ return new MessageTemplate.Builder(
+ getCarContext().getString(R.string.sign_in_complete_text))
+ .setTitle(getCarContext().getString(R.string.sign_in_complete_title))
+ .setHeaderAction(Action.BACK)
+ .addAction(new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.sign_out_action_title))
+ .setOnClickListener(() -> {
+ mState = State.USERNAME;
+ invalidate();
+ })
+ .build())
+ .build();
+ }
+
+ private enum State {
+ USERNAME,
+ PASSWORD,
+ PIN,
+ PROVIDER,
+ QR_CODE,
+ SIGNED_IN,
+ }
+
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java
new file mode 100644
index 0000000..96cab04
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import android.os.Bundle;
+
+import androidx.activity.ComponentActivity;
+import androidx.annotation.Nullable;
+
+/**
+ * An activity for use by the car app library to perform actions such as requesting permissions.
+ */
+public class SignInWithGoogleActivity extends ComponentActivity {
+ static final String BINDER_KEY = "binder";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+// OnSignInComplete signInCompleteCallback =
+// (OnSignInComplete) getIntent().getExtras().getBinder(BINDER_KEY);
+//
+// ActivityResultLauncher<Intent> activityResultLauncher =
+// registerForActivityResult(
+// new ActivityResultContracts.StartActivityForResult(),
+// result -> {
+// GoogleSignInAccount account =
+// GoogleSignIn.getSignedInAccountFromIntent(
+// result.getData()).getResult();
+// signInCompleteCallback.onSignInComplete(account);
+// finish();
+// });
+//
+// GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
+// if (account != null) {
+// signInCompleteCallback.onSignInComplete(account);
+// finish();
+// }
+//
+// GoogleSignInOptions gso =
+// new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+// .requestEmail()
+// .requestProfile()
+// .build();
+// GoogleSignInClient signInClient = GoogleSignIn.getClient(this, gso);
+// activityResultLauncher.launch(signInClient.getSignInIntent());
+ }
+
+
+// /**
+// * Binder callback to provide to the sign in activity.
+// */
+// abstract static class OnSignInComplete extends Binder implements IBinder {
+// /**
+// * Notifies that sign in flow completed.
+// *
+// * @param account the account signed in or {@code null} if there were issues signing in.
+// */
+// public abstract void onSignInComplete(@Nullable GoogleSignInAccount account);
+// }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
new file mode 100644
index 0000000..1dfacb0
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_SHORT;
+import static androidx.car.app.model.Action.BACK;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.GridItem;
+import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.OnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** Creates a screen that demonstrates usage of the full screen {@link GridTemplate}. */
+public final class GridTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+ private static final int MAX_GRID_ITEMS = 100;
+ private static final int LOADING_TIME_MILLIS = 2000;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @Nullable
+ private IconCompat mImage;
+ @Nullable
+ private IconCompat mIcon;
+ private boolean mIsFourthItemLoading;
+ private boolean mThirdItemToggleState;
+ private boolean mFourthItemToggleState;
+ private boolean mFifthItemToggleState;
+
+ public GridTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ mIsFourthItemLoading = false;
+ mThirdItemToggleState = false;
+ mFourthItemToggleState = true;
+ mFifthItemToggleState = false;
+ }
+
+ @Override
+ public void onCreate(@NonNull LifecycleOwner owner) {
+ Resources resources = getCarContext().getResources();
+ Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_image_square);
+ mImage = IconCompat.createWithBitmap(bitmap);
+ mIcon = IconCompat.createWithResource(getCarContext(), R.drawable.ic_fastfood_white_48dp);
+ }
+
+ @Override
+ @SuppressWarnings({"FutureReturnValueIgnored"})
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mIsFourthItemLoading = false;
+
+ // Post a message that starts loading the fourth item for some time.
+ triggerFourthItemLoading();
+ }
+
+ private GridItem createGridItem(int index) {
+ switch (index) {
+ case 0:
+ // Grid item with an icon and a title.
+ return createGridItem(createCarIconFromImage(mIcon), GridItem.IMAGE_TYPE_ICON,
+ getTextStringFromId(R.string.non_actionable));
+ case 1:
+ // Grid item with an icon, a title, onClickListener and no text.
+ return createGridItem(createCarIconFromImage(mIcon), GridItem.IMAGE_TYPE_ICON,
+ getTextStringFromId(R.string.second_item), createOnClickListener(
+ (String) getTextStringFromId(R.string.second_item_toast_msg),
+ LENGTH_SHORT));
+
+ case 2:
+ // Grid item with an icon marked as icon, a title, a text and a toggle in
+ // unchecked state.
+ return createGridItem(createCarIconFromImage(mIcon),
+ GridItem.IMAGE_TYPE_LARGE, getTextStringFromId(R.string.third_item),
+ mThirdItemToggleState
+ ? getTextStringFromId(R.string.checked_action_title) :
+ getTextStringFromId(R.string.unchecked_action_title),
+ createOnClickListenerForThirdItem());
+
+ case 3:
+ // Grid item with an image, a title, a long text and a toggle that takes some
+ // time to
+ // update.
+ if (mIsFourthItemLoading) {
+ return createGridItem(getTextStringFromId(R.string.fourth_item),
+ mFourthItemToggleState
+ ? getTextStringFromId(R.string.on_action_title) :
+ getTextStringFromId(R.string.off_action_title),
+ true);
+ } else {
+ return createGridItem(createCarIconFromImage(mImage),
+ getTextStringFromId(R.string.fourth_item),
+ mFourthItemToggleState
+ ? getTextStringFromId(R.string.on_action_title) :
+ getTextStringFromId(R.string.off_action_title),
+ this::triggerFourthItemLoading);
+ }
+ case 4:
+ // Grid item with a large image, a long title, no text and a toggle in unchecked
+ // state.
+ return createGridItem(createCarIconFromImage(mIcon),
+ GridItem.IMAGE_TYPE_LARGE, getTextStringFromId(R.string.fifth_item),
+ createOnClickListenerForFifthItem());
+ case 5:
+ // Grid item with an image marked as an icon, a long title, a long text and
+ // onClickListener.
+ return createGridItem(createCarIconFromImage(mIcon),
+ GridItem.IMAGE_TYPE_ICON, getTextStringFromId(R.string.sixth_item),
+ getTextStringFromId(R.string.sixth_item),
+ createOnClickListener(
+ (String) getTextStringFromId(R.string.sixth_item_toast_msg),
+ LENGTH_SHORT));
+
+ default:
+ String titleText = (index + 1) + "th item";
+ String toastText = "Clicked " + (index + 1) + "th item";
+
+ return createGridItem(createCarIconFromImage(mIcon),
+ GridItem.IMAGE_TYPE_ICON, titleText, createOnClickListener(toastText,
+ LENGTH_SHORT));
+ }
+ }
+
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ int itemLimit = 6;
+ // Adjust the item limit according to the car constrains.
+ if (getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
+ itemLimit =
+ Math.min(MAX_GRID_ITEMS,
+ getCarContext().getCarService(ConstraintManager.class).getContentLimit(
+ ConstraintManager.CONTENT_LIMIT_TYPE_GRID));
+ }
+
+ ItemList.Builder gridItemListBuilder = new ItemList.Builder();
+ for (int i = 0; i <= itemLimit; i++) {
+ gridItemListBuilder.addItem(createGridItem(i));
+ }
+
+ Action settings = new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.settings_action_title))
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(R.string.settings_toast_msg),
+ LENGTH_SHORT)
+ .show())
+ .build();
+ return new GridTemplate.Builder()
+ .setHeaderAction(Action.APP_ICON)
+ .setSingleList(gridItemListBuilder.build())
+ .setTitle(getCarContext().getString(R.string.grid_template_demo_title))
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(settings)
+ .build())
+ .setHeaderAction(BACK)
+ .build();
+ }
+
+ /**
+ * Changes the fourth item to a loading state for some time and changes it back to the loaded
+ * state.
+ */
+ private void triggerFourthItemLoading() {
+ mHandler.post(
+ () -> {
+ mIsFourthItemLoading = true;
+ invalidate();
+
+ mHandler.postDelayed(
+ () -> {
+ mIsFourthItemLoading = false;
+ mFourthItemToggleState = !mFourthItemToggleState;
+ invalidate();
+ },
+ LOADING_TIME_MILLIS);
+ });
+ }
+
+ /**
+ * Custom listener for third grid item
+ */
+ private OnClickListener createOnClickListenerForThirdItem() {
+ return () -> {
+ mThirdItemToggleState = !mThirdItemToggleState;
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.third_item_checked_toast_msg)
+ + ": " + mThirdItemToggleState,
+ LENGTH_SHORT)
+ .show();
+ invalidate();
+ };
+ }
+
+ /**
+ * Custom listener for fifth grid item
+ */
+ private OnClickListener createOnClickListenerForFifthItem() {
+ return () -> {
+ mFifthItemToggleState = !mFifthItemToggleState;
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.fifth_item_checked_toast_msg)
+ + ": "
+ + mFifthItemToggleState,
+ LENGTH_SHORT)
+ .show();
+ invalidate();
+ };
+ }
+
+ /**
+ * Returns CharSequence text for given id
+ */
+ private CharSequence getTextStringFromId(int id) {
+ return getCarContext().getString(id);
+ }
+
+ /**
+ * Create an onClickListener using a Car Toast
+ */
+ private OnClickListener createOnClickListener(String toastText, int toastLength) {
+ return () ->
+ CarToast.makeText(
+ getCarContext(),
+ toastText,
+ toastLength)
+ .show();
+ }
+
+ /**
+ * Creates CarIcon from given IconCompat image
+ */
+ private CarIcon createCarIconFromImage(IconCompat image) {
+ return new CarIcon.Builder(image).build();
+ }
+
+ /**
+ * A number of overloaded constructors with different parameters to build grid items
+ */
+ private GridItem createGridItem(CharSequence title, CharSequence text,
+ boolean isLoading) {
+ return new GridItem.Builder()
+ .setTitle(title)
+ .setText(text)
+ .setLoading(isLoading)
+ .build();
+ }
+
+ private GridItem createGridItem(CarIcon carIcon,
+ int imageType, CharSequence title, CharSequence text,
+ OnClickListener onClickListener) {
+ return new GridItem.Builder()
+ .setImage(carIcon, imageType)
+ .setTitle(title)
+ .setText(text)
+ .setOnClickListener(onClickListener)
+ .build();
+ }
+
+ private GridItem createGridItem(CarIcon carIcon,
+ CharSequence title, CharSequence text,
+ OnClickListener onClickListener) {
+ return new GridItem.Builder()
+ .setImage(carIcon)
+ .setTitle(title)
+ .setText(text)
+ .setOnClickListener(onClickListener)
+ .build();
+ }
+
+ private GridItem createGridItem(CarIcon carIcon,
+ int imageType, CharSequence title,
+ OnClickListener onClickListener) {
+ return new GridItem.Builder()
+ .setImage(carIcon, imageType)
+ .setTitle(title)
+ .setOnClickListener(onClickListener)
+ .build();
+ }
+
+ private GridItem createGridItem(CarIcon carIcon,
+ int imageType, CharSequence title) {
+ return new GridItem.Builder()
+ .setImage(carIcon, imageType)
+ .setTitle(title)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
new file mode 100644
index 0000000..3753600
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.GridItem;
+import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.Template;
+import androidx.car.app.notification.CarAppExtender;
+import androidx.car.app.notification.CarNotificationManager;
+import androidx.car.app.notification.CarPendingIntent;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.core.app.NotificationChannelCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A simple screen that demonstrates how to use notifications in a car app. */
+public final class NotificationDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+ static final long NOTIFICATION_DELAY_IN_MILLIS = SECONDS.toMillis(1);
+ private static final String NOTIFICATION_CHANNEL_ID = "channel_00";
+ private static final CharSequence NOTIFICATION_CHANNEL_NAME = "Default Channel";
+ private static final int NOTIFICATION_ID = 1001;
+ private static final String NOTIFICATION_CHANNEL_HIGH_ID = "channel_01";
+ private static final CharSequence NOTIFICATION_CHANNEL_HIGH_NAME = "High Channel";
+ private static final String NOTIFICATION_CHANNEL_LOW_ID = "channel_02";
+ private static final CharSequence NOTIFICATION_CHANNEL_LOW_NAME = "Low Channel";
+ private static final String INTENT_ACTION_PRIMARY_PHONE =
+ "androidx.car.app.sample.showcase.common.INTENT_ACTION_PRIMARY_PHONE";
+ private static final String INTENT_ACTION_SECONDARY_PHONE =
+ "androidx.car.app.sample.showcase.common.INTENT_ACTION_SECONDARY_PHONE";
+ private static final int MSG_SEND_NOTIFICATION = 1;
+ final Handler mHandler = new Handler(Looper.getMainLooper(), new HandlerCallback());
+
+ private final IconCompat mIcon = IconCompat.createWithResource(getCarContext(),
+ R.drawable.ic_face_24px);
+ /** A broadcast receiver that can show a toast message upon receiving a broadcast. */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(R.string.triggered_toast_msg) + ": "
+ + intent.getAction(),
+ CarToast.LENGTH_SHORT)
+ .show();
+ }
+ };
+ private int mImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ private boolean mIsNavCategory;
+ private boolean mSetOngoing;
+ private int mNotificationCount;
+
+ public NotificationDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ getLifecycle().addObserver(this);
+ }
+
+ @Override
+ public void onCreate(@NonNull LifecycleOwner owner) {
+ registerBroadcastReceiver();
+ }
+
+ @Override
+ public void onDestroy(@NonNull LifecycleOwner owner) {
+ mHandler.removeMessages(MSG_SEND_NOTIFICATION);
+ CarNotificationManager.from(getCarContext()).cancelAll();
+ unregisterBroadcastReceiver();
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ // Send a single notification with the settings configured by other buttons.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setTitle(getCarContext().getString(R.string.send_notification_title))
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setOnClickListener(this::sendNotification)
+ .build());
+
+ // Start a repeating notification with the settings configured by other buttons.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setTitle(getCarContext().getString(R.string.start_notifications_title))
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setOnClickListener(() -> mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_SEND_NOTIFICATION)))
+ .build());
+
+ // Stop the repeating notification and reset the count.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setTitle(getCarContext().getString(R.string.stop_notifications_title))
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setOnClickListener(() -> {
+ mHandler.removeMessages(MSG_SEND_NOTIFICATION);
+ CarNotificationManager.from(getCarContext()).cancelAll();
+ mNotificationCount = 0;
+ })
+ .build());
+
+ // Configure the notification importance.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setTitle(getCarContext().getString(R.string.importance_title))
+ .setText(getImportanceString())
+ .setOnClickListener(() -> {
+ setImportance();
+ invalidate();
+ })
+ .build());
+
+ // Configure whether the notification's category is navigation.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setTitle(getCarContext().getString(R.string.category_title))
+ .setText(getCategoryString())
+ .setOnClickListener(() -> {
+ mIsNavCategory = !mIsNavCategory;
+ invalidate();
+ })
+ .build());
+
+ // Configure whether the notification is an ongoing notification.
+ listBuilder.addItem(
+ new GridItem.Builder()
+ .setImage(new CarIcon.Builder(mIcon).build())
+ .setTitle(getCarContext().getString(R.string.ongoing_title))
+ .setText(String.valueOf(mSetOngoing))
+ .setOnClickListener(() -> {
+ mSetOngoing = !mSetOngoing;
+ invalidate();
+ })
+ .build());
+
+ return new GridTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.notification_demo))
+ .setHeaderAction(Action.BACK)
+ .build();
+ }
+
+ void sendNotification() {
+ mNotificationCount++;
+ String title =
+ getCarContext().getString(R.string.notification_title) + ": "
+ + getImportanceString() + ", " + mNotificationCount;
+ String text = getCarContext().getString(R.string.category_title) + ": "
+ + getCategoryString() + ", "
+ + getCarContext().getString(R.string.ongoing_title) + ": " + mSetOngoing;
+
+ switch (mImportance) {
+ case NotificationManager.IMPORTANCE_HIGH:
+ sendNotification(title, text, NOTIFICATION_CHANNEL_HIGH_ID,
+ NOTIFICATION_CHANNEL_HIGH_NAME, NOTIFICATION_ID,
+ NotificationManager.IMPORTANCE_HIGH);
+ break;
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ sendNotification(title, text, NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
+ NOTIFICATION_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ break;
+ case NotificationManager.IMPORTANCE_LOW:
+ sendNotification(title, text, NOTIFICATION_CHANNEL_LOW_ID,
+ NOTIFICATION_CHANNEL_LOW_NAME, NOTIFICATION_ID,
+ NotificationManager.IMPORTANCE_LOW);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Suppressing 'ObsoleteSdkInt' as this code is shared between APKs with different min SDK
+ // levels
+ @SuppressLint("ObsoleteSdkInt")
+ private void sendNotification(CharSequence title, CharSequence text, String channelId,
+ CharSequence channelName, int notificationId, int importance) {
+ CarNotificationManager carNotificationManager =
+ CarNotificationManager.from(getCarContext());
+
+ NotificationChannelCompat channel = new NotificationChannelCompat.Builder(channelId,
+ importance).setName(channelName).build();
+ carNotificationManager.createNotificationChannel(channel);
+
+ NotificationCompat.Builder builder;
+ builder = new NotificationCompat.Builder(getCarContext(), channelId);
+ if (mIsNavCategory) {
+ builder.setCategory(NotificationCompat.CATEGORY_NAVIGATION);
+ }
+ builder.setOngoing(mSetOngoing);
+
+ builder.setSmallIcon(R.drawable.ic_bug_report_24px)
+ .setContentTitle(title + " (phone)")
+ .setContentText(text + " (phone)")
+ .setColor(getCarContext().getColor(androidx.car.app.R.color.carColorGreen))
+ .setColorized(true)
+ .setLargeIcon(
+ BitmapFactory.decodeResource(
+ getCarContext().getResources(), R.drawable.ic_hi))
+ .addAction(
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_face_24px,
+ "Action1 (phone)",
+ createPendingIntent(INTENT_ACTION_PRIMARY_PHONE))
+ .build())
+ .addAction(
+ R.drawable.ic_commute_24px,
+ "Action2 (phone)",
+ createPendingIntent(INTENT_ACTION_SECONDARY_PHONE))
+ .extend(
+ new CarAppExtender.Builder()
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(
+ CarPendingIntent.getCarApp(getCarContext(), 0,
+ new Intent(Intent.ACTION_VIEW).setComponent(
+ new ComponentName(getCarContext(),
+ ShowcaseService.class)), 0))
+ .setColor(CarColor.PRIMARY)
+ .setSmallIcon(R.drawable.ic_bug_report_24px)
+ .setLargeIcon(
+ BitmapFactory.decodeResource(
+ getCarContext().getResources(),
+ R.drawable.ic_hi))
+ .addAction(
+ R.drawable.ic_commute_24px,
+ getCarContext().getString(R.string.navigate),
+ getPendingIntentForNavigation())
+ .addAction(
+ R.drawable.ic_face_24px,
+ getCarContext().getString(R.string.call_action_title),
+ createPendingIntentForCall())
+ .build());
+
+ carNotificationManager.notify(notificationId, builder);
+ }
+
+ private PendingIntent createPendingIntentForCall() {
+ Intent intent = new Intent(Intent.ACTION_DIAL).setData(Uri.parse("tel:+14257232350"));
+ return CarPendingIntent.getCarApp(getCarContext(), intent.hashCode(), intent, 0);
+ }
+
+ private PendingIntent getPendingIntentForNavigation() {
+ Intent intent = new Intent(CarContext.ACTION_NAVIGATE).setData(Uri.parse("geo:0,0?q=Home"));
+ return CarPendingIntent.getCarApp(getCarContext(), intent.hashCode(), intent, 0);
+ }
+
+ private String getImportanceString() {
+ switch (mImportance) {
+ case NotificationManager.IMPORTANCE_HIGH:
+ return getCarContext().getString(R.string.high_importance);
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ return getCarContext().getString(R.string.default_importance);
+ case NotificationManager.IMPORTANCE_LOW:
+ return getCarContext().getString(R.string.low_importance);
+ default:
+ return getCarContext().getString(R.string.unknown_importance);
+ }
+ }
+
+ private String getCategoryString() {
+ return mIsNavCategory ? "Navigation" : "None";
+ }
+
+ /**
+ * Change the notification importance in a rotating sequence:
+ * Low -> Default -> High -> Low...
+ */
+ private void setImportance() {
+ switch (mImportance) {
+ case NotificationManager.IMPORTANCE_HIGH:
+ mImportance = NotificationManager.IMPORTANCE_LOW;
+ break;
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ mImportance = NotificationManager.IMPORTANCE_HIGH;
+ break;
+ case NotificationManager.IMPORTANCE_LOW:
+ mImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_ACTION_PRIMARY_PHONE);
+ filter.addAction(INTENT_ACTION_SECONDARY_PHONE);
+
+ getCarContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ getCarContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ /** Returns a pending intent with the provided intent action. */
+ private PendingIntent createPendingIntent(String intentAction) {
+ Intent intent = new Intent(intentAction);
+ return PendingIntent.getBroadcast(getCarContext(), intentAction.hashCode(), intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ final class HandlerCallback implements Handler.Callback {
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_SEND_NOTIFICATION) {
+ sendNotification();
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_SEND_NOTIFICATION),
+ NOTIFICATION_DELAY_IN_MILLIS);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
new file mode 100644
index 0000000..e94d105e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.model.Action.BACK;
+
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.HostInfo;
+import androidx.car.app.Screen;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Creates a screen that demonstrate the image loading in the library using a content provider. */
+public final class ContentProviderIconsDemoScreen extends Screen {
+ private static final int[] ICON_DRAWABLES = {
+ R.drawable.arrow_right_turn, R.drawable.arrow_straight, R.drawable.ic_i5,
+ R.drawable.ic_520
+ };
+ @Nullable
+ private final String mHostPackageName;
+
+ public ContentProviderIconsDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+
+ HostInfo hostInfo = carContext.getHostInfo();
+ mHostPackageName = hostInfo == null ? null : hostInfo.getPackageName();
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ String hostPackageName = mHostPackageName;
+ if (hostPackageName == null) {
+ // Cannot get the host package name, show an error message.
+ listBuilder.setNoItemsMessage(
+ getCarContext().getString(R.string.images_unknown_host_error));
+ } else {
+ for (int i = 0; i < ICON_DRAWABLES.length; i++) {
+ int resId = ICON_DRAWABLES[i];
+ Uri uri = DelayedFileProvider.getUriForResource(getCarContext(), hostPackageName,
+ resId);
+ listBuilder.addItem(
+ new Row.Builder()
+ .setImage(
+ new CarIcon.Builder(
+ IconCompat.createWithContentUri(uri))
+ .build())
+ .setTitle(
+ getCarContext().getString(R.string.icon_title_prefix) + " "
+ + i)
+ .build());
+ }
+ }
+
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.content_provider_icons_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java
new file mode 100644
index 0000000..217bd16
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.ThreadLocalRandom;
+
+/** A simple file provider that returns files after a random delay. */
+public class DelayedFileProvider extends FileProvider {
+ private static final String FILE_PROVIDER_AUTHORITY = "com.showcase.fileprovider";
+ private static final String RESOURCE_DIR = "res";
+ private static final long MIN_DELAY_MILLIS = 1000;
+ private static final long MAX_DELAY_MILLIS = 3000;
+
+ /** Creates a file from the given resource id and returns the URI for it. */
+ @NonNull
+ public static Uri getUriForResource(@NonNull Context context,
+ @NonNull String hostPackageName, int resId) {
+ File resourceFile =
+ new File(context.getFilesDir().getAbsolutePath(), RESOURCE_DIR + "/" + resId);
+ if (!resourceFile.exists()) {
+ resourceFile.getParentFile().mkdir();
+
+ Bitmap bm = BitmapFactory.decodeResource(context.getResources(), resId);
+ try (FileOutputStream fos = new FileOutputStream(resourceFile)) {
+ bm.compress(CompressFormat.PNG, 10, fos);
+ } catch (IOException ex) {
+ throw new IllegalArgumentException("Invalid resource " + resId);
+ }
+ }
+ Uri uri = getUriForFile(context, FILE_PROVIDER_AUTHORITY, resourceFile);
+
+ // FileProvider requires the app to grant temporary access to the car hosts for the file.
+ // A URI from a content provider may not need to do this if its contents are public.
+ context.grantUriPermission(hostPackageName, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ return uri;
+ }
+
+ @Override
+ @NonNull
+ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+ throws FileNotFoundException {
+ try {
+ // Wait for a random period between the minimum and maximum delay.
+ Thread.sleep(ThreadLocalRandom.current().nextLong(MIN_DELAY_MILLIS, MAX_DELAY_MILLIS));
+ } catch (InterruptedException e) {
+ throw new FileNotFoundException(e.getMessage());
+ }
+
+ return super.openFile(uri, mode);
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
new file mode 100644
index 0000000..df4d004
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.SectionedItemList;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen demonstrating selectable lists. */
+public final class RadioButtonListDemoScreen extends Screen {
+
+ public RadioButtonListDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ private boolean mIsEnabled = true;
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ ListTemplate.Builder templateBuilder = new ListTemplate.Builder();
+ ItemList radioList =
+ new ItemList.Builder()
+
+ .addItem(buildRowForTemplate(R.string.option_row_radio_title,
+ R.string.additional_text))
+
+ .addItem(buildRowForTemplate(R.string.option_row_radio_icon_title,
+ R.string.additional_text,
+ buildImageWithResource(R.drawable
+ .ic_fastfood_white_48dp), Row.IMAGE_TYPE_ICON))
+
+ .addItem(buildRowForTemplate(
+ R.string.option_row_radio_icon_colored_text_title,
+ R.string.additional_text,
+ buildImageWithResource(R.drawable
+ .test_image_square), Row.IMAGE_TYPE_LARGE))
+
+ .setOnSelectedListener(this::onSelected)
+ .build();
+ templateBuilder.addSectionedList(
+ SectionedItemList.create(radioList,
+ getCarContext().getString(R.string.sample_additional_list)));
+
+ return templateBuilder
+ .setTitle(getCarContext().getString(R.string.radio_button_list_demo_title))
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setTitle(mIsEnabled
+ ? getCarContext().getString(
+ R.string.disable_all_rows)
+ : getCarContext().getString(
+ R.string.enable_all_rows))
+ .setOnClickListener(
+ () -> {
+ mIsEnabled = !mIsEnabled;
+ invalidate();
+ })
+ .build())
+ .build())
+ .setHeaderAction(
+ BACK).build();
+ }
+
+ private CarIcon buildImageWithResource(int imageId) {
+ return new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ imageId))
+ .build();
+ }
+
+ private Row buildRowForTemplate(int title, int text, CarIcon icon, int imageType) {
+ return new Row.Builder()
+ .addText(getCarContext().getString(text))
+ .setTitle(getCarContext().getString(title))
+ .setImage(icon, imageType)
+ .setEnabled(mIsEnabled)
+ .build();
+
+ }
+
+ private Row buildRowForTemplate(int title, int text) {
+ return new Row.Builder()
+ .addText(getCarContext().getString(text))
+ .setTitle(getCarContext().getString(title))
+ .setEnabled(mIsEnabled)
+ .build();
+
+ }
+
+ private void onSelected(int index) {
+ CarToast.makeText(getCarContext(),
+ getCarContext()
+ .getString(R.string.changes_selection_to_index_toast_msg_prefix)
+ + ":"
+ + " " + index, LENGTH_LONG)
+ .show();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
new file mode 100644
index 0000000..ceb70e06
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.CarColor.GREEN;
+import static androidx.car.app.model.CarColor.RED;
+import static androidx.car.app.model.CarColor.YELLOW;
+
+import android.graphics.BitmapFactory;
+import android.text.SpannableString;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.common.Utils;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Creates a screen that shows different types of texts and icons. */
+public final class TextAndIconsDemosScreen extends Screen {
+
+ private static final String FULL_STAR = "\u2605";
+ private static final String HALF_STAR = "\u00BD";
+
+ public TextAndIconsDemosScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+
+ ItemList.Builder listBuilder = new ItemList.Builder();
+
+ listBuilder.addItem(buildRowForTemplate(R.string.title_with_app_icon_row_title,
+ CarIcon.APP_ICON));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.png_bitmap_title,
+ buildCarIconWithBitmap(R.drawable.banana)));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.png_res_title,
+ buildCarIconWithResource(R.drawable.banana)));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.title_with_res_id_image_row_title,
+ buildSecondaryText(R.string.example_1_text, RED, 16, 3),
+ buildCarIconWithResource(R.drawable.ic_fastfood_white_48dp, RED)));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.title_with_svg_image_row_title,
+ buildSecondaryText(R.string.example_2_text, GREEN, 16, 5),
+ buildCarIconWithResource(R.drawable.ic_emoji_food_beverage_white_48dp, GREEN)));
+
+ listBuilder.addItem(buildRowForTemplate(R.string.colored_secondary_row_title,
+ getRatingsString(3.5)));
+
+ return new ListTemplate.Builder()
+ .setSingleList(listBuilder.build())
+ .setTitle(getCarContext().getString(R.string.text_icons_demo_title))
+ .setHeaderAction(BACK)
+ .build();
+
+ }
+
+ private CarIcon buildCarIconWithResource(int imageId) {
+ return new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ imageId))
+ .build();
+ }
+
+ private CarIcon buildCarIconWithResource(int imageId, CarColor color) {
+ return new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ imageId))
+ .setTint(color)
+ .build();
+ }
+
+ private CarIcon buildCarIconWithBitmap(int imageId) {
+ return new CarIcon.Builder(
+ IconCompat.createWithBitmap(
+ BitmapFactory.decodeResource(
+ getCarContext().getResources(),
+ imageId)))
+ .build();
+ }
+
+ /**
+ * build a colored line of secondary text using a specific CarColor and some custom text
+ */
+ private CharSequence buildSecondaryText(int textId, CarColor color, int index, int length) {
+ return Utils.colorize(getCarContext().getString(textId),
+ color, index, length);
+ }
+
+ private Row buildRowForTemplate(int title, CharSequence text) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .addText(text)
+ .build();
+ }
+
+ private Row buildRowForTemplate(int title, CarIcon image) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .setImage(image)
+ .build();
+ }
+
+ private Row buildRowForTemplate(int title, CharSequence text, CarIcon image) {
+ return new Row.Builder()
+ .setTitle(getCarContext().getString(title))
+ .addText(text)
+ .setImage(image)
+ .build();
+ }
+
+ private static CharSequence getRatingsString(Double ratings) {
+ String s;
+ double r;
+ for (s = "", r = ratings; r > 0; --r) {
+ s += r < 1 ? HALF_STAR : FULL_STAR;
+ }
+ SpannableString ss = new SpannableString(s + " ratings: " + ratings);
+ if (!s.isEmpty()) {
+ Utils.colorize(ss, YELLOW, 0, s.length());
+ }
+ return ss;
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
new file mode 100644
index 0000000..092b530
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.CarColor.GREEN;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.OnClickListener;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.Toggle;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen demonstrating selectable lists. */
+public final class ToggleButtonListDemoScreen extends Screen {
+
+ public ToggleButtonListDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ private boolean mFirstToggleState;
+ private boolean mSecondToggleState;
+ private boolean mSecondToggleEnabled = true;
+ private int mImageType = Row.IMAGE_TYPE_ICON;
+ private boolean mSetTintToVector;
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ Toggle mToggleForVector = new Toggle.Builder((checked) -> {
+ mSetTintToVector = !mSetTintToVector;
+ invalidate();
+ }).setChecked(mSetTintToVector).build();
+
+ Toggle mFirstToggle = new Toggle.Builder((checked) -> {
+ mSecondToggleEnabled = checked;
+ if (checked) {
+ CarToast.makeText(getCarContext(), R.string.toggle_test_enabled,
+ LENGTH_LONG).show();
+ } else {
+ CarToast.makeText(getCarContext(), R.string.toggle_test_disabled,
+ LENGTH_LONG).show();
+ }
+ mFirstToggleState = !mFirstToggleState;
+ invalidate();
+ }).setChecked(mFirstToggleState).build();
+
+ Toggle mSecondToggle = new Toggle.Builder((checked) -> {
+ mSecondToggleState = !mSecondToggleState;
+ invalidate();
+ }).setChecked(mSecondToggleState).setEnabled(mSecondToggleEnabled).build();
+
+ ItemList.Builder builder = new ItemList.Builder();
+ builder.addItem(buildRowForTemplate(R.string.toggle_test_first_toggle_title,
+ R.string.toggle_test_first_toggle_text, mFirstToggle));
+
+ builder.addItem(buildRowForTemplate(R.string.toggle_test_second_toggle_title,
+ R.string.toggle_test_second_toggle_text, mSecondToggle));
+
+ builder.addItem(buildRowForTemplate(titleForVectorDrawable(),
+ R.string.vector_toggle_details, mToggleForVector, buildCarIconForVectorDrawable(),
+ null, Row.IMAGE_TYPE_ICON));
+
+ builder.addItem(buildRowForTemplate(R.string.image_test_title,
+ R.string.image_test_text, null, buildCarIconForImageTest(),
+ buildOnClickListenerForImageTest(), mImageType));
+
+ return new ListTemplate.Builder()
+ .setSingleList(builder.build())
+ .setTitle(getCarContext().getString(R.string.toggle_button_demo_title))
+ .setHeaderAction(BACK)
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.home_caps_action_title))
+ .setOnClickListener(
+ () -> getScreenManager().popToRoot())
+ .build())
+ .build())
+ .build();
+ }
+
+ private CarIcon buildCarIconForVectorDrawable() {
+ CarIcon.Builder carIconBuilder = new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_fastfood_white_48dp));
+
+ if (mSetTintToVector) {
+ carIconBuilder.setTint(GREEN);
+ }
+ return carIconBuilder.build();
+ }
+
+ private CarIcon buildCarIconForImageTest() {
+ return new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_fastfood_yellow_48dp))
+ .build();
+ }
+
+ private int titleForVectorDrawable() {
+ if (mSetTintToVector) {
+ return R.string.vector_with_tint_title;
+ } else {
+ return R.string.vector_no_tint_title;
+ }
+ }
+
+ private OnClickListener buildOnClickListenerForImageTest() {
+ return () -> {
+ mImageType =
+ mImageType == Row.IMAGE_TYPE_ICON
+ ? Row.IMAGE_TYPE_LARGE
+ : Row.IMAGE_TYPE_ICON;
+ invalidate();
+ };
+ }
+
+ private Row buildRowForTemplate(int title, int text, Toggle toggle) {
+ return new Row.Builder()
+ .addText(getCarContext().getString(text))
+ .setTitle(getCarContext().getString(title))
+ .setToggle(toggle)
+ .build();
+
+ }
+
+ private Row buildRowForTemplate(int title, int text, Toggle toggle, CarIcon icon,
+ OnClickListener onClickListener, int imageType) {
+
+ Row.Builder rowBuilder = new Row.Builder();
+ rowBuilder
+ .addText(getCarContext().getString(text))
+ .setTitle(getCarContext().getString(title))
+ .setImage(icon, imageType);
+
+ if (toggle != null) rowBuilder.setToggle(toggle);
+ if (onClickListener != null) rowBuilder.setOnClickListener(onClickListener);
+
+ return rowBuilder.build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java
new file mode 100644
index 0000000..03e5598
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.LongMessageTemplate;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+
+/** A screen that demonstrates the long message template. */
+public class LongMessageTemplateDemoScreen extends Screen {
+ public LongMessageTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
+ return new MessageTemplate.Builder(
+ getCarContext().getString(R.string.long_msg_template_not_supported_text))
+ .setTitle(getCarContext().getString(
+ R.string.long_msg_template_not_supported_title))
+ .setHeaderAction(Action.BACK)
+ .build();
+ }
+
+ Action.Builder primaryActionBuilder = new Action.Builder()
+ .setOnClickListener(
+ ParkedOnlyOnClickListener.create(() -> {
+ getScreenManager().pop();
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(R.string.primary_action_title),
+ LENGTH_LONG
+ ).show();
+ }))
+ .setTitle(getCarContext().getString(R.string.accept_action_title));
+ if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+ primaryActionBuilder.setFlags(FLAG_PRIMARY);
+ }
+
+ return new LongMessageTemplate.Builder(
+ getCarContext().getString(R.string.long_msg_template_text))
+ .setTitle(getCarContext().getString(R.string.long_msg_template_demo_title))
+ .setHeaderAction(BACK)
+ .addAction(primaryActionBuilder.build())
+ .addAction(new Action.Builder()
+ .setBackgroundColor(CarColor.RED)
+ .setOnClickListener(
+ ParkedOnlyOnClickListener.create(() -> getScreenManager().pop()))
+ .setTitle(getCarContext().getString(R.string.reject_action_title))
+ .build())
+ .setActionStrip(new ActionStrip.Builder()
+ .addAction(new Action.Builder()
+ .setTitle(getCarContext().getString(R.string.more_action_title))
+ .setOnClickListener(
+ () ->
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.more_toast_msg),
+ LENGTH_LONG)
+ .show())
+ .build())
+ .build())
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java
new file mode 100644
index 0000000..f24d545
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2022 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.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen that demonstrates the message template. */
+public class ShortMessageTemplateDemoScreen extends Screen {
+
+ public ShortMessageTemplateDemoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ Action.Builder primaryActionBuilder = new Action.Builder()
+ .setOnClickListener(() -> {
+ CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(R.string.primary_action_title),
+ LENGTH_LONG
+ ).show();
+ })
+ .setTitle(getCarContext().getString(R.string.ok_action_title));
+ if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+ primaryActionBuilder.setFlags(FLAG_PRIMARY);
+ }
+
+ Action settings = new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.settings_action_title))
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ getCarContext(),
+ getCarContext().getString(
+ R.string.settings_toast_msg),
+ LENGTH_LONG)
+ .show())
+ .build();
+
+ return new MessageTemplate.Builder(
+ getCarContext().getString(R.string.msg_template_demo_text))
+ .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_emoji_food_beverage_white_48dp))
+ .setTint(CarColor.GREEN)
+ .build())
+ .setHeaderAction(BACK)
+ .addAction(primaryActionBuilder.build())
+ .addAction(
+ new Action.Builder()
+ .setBackgroundColor(CarColor.RED)
+ .setTitle(getCarContext().getString(R.string.throw_action_title))
+ .setOnClickListener(
+ () -> {
+ throw new RuntimeException("Error");
+ })
+ .build())
+
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(settings)
+ .build())
+ .build();
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
index 45f07ef..9a75cf192 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
@@ -113,7 +113,7 @@
<string name="example_2_text">This text has a green color</string>
<string name="example_3_text">This text has a blue color</string>
<string name="example_4_text">This text has a yellow color</string>
- <string name="example_5_text">This text uses the primary colo</string>
+ <string name="example_5_text">This text uses the primary color</string>
<string name="example_6_text">This text uses the secondary color</string>
<string name="color_demo">Color Demo</string>
@@ -291,6 +291,7 @@
<!-- MessageTemplateDemoScreen -->
<string name="msg_template_demo_title">Message Template Demo</string>
<string name="msg_template_demo_text">Message goes here.\nMore text on second line.</string>
+ <string name="short_msg_template_demo_title">Short Message Template Demo</string>
<!-- PaneTemplateDemoScreen -->
<string name="pane_template_demo_title">Pane Template Demo</string>
@@ -338,7 +339,7 @@
<string name="vector_with_tint_title">A vector drawable, with a tint</string>
<string name="vector_with_app_theme_attr_title">A vector drawable, with an app\'s theme attribute for its color</string>
<string name="png_res_title">A PNG, sent as a resource</string>
- <string name="png_bitmap_title">A PNG, sent as a resource</string>
+ <string name="png_bitmap_title">A PNG, sent as a bitmap</string>
<!-- RowDemoScreen -->
<string name="just_row_title">Just a title</string>
@@ -356,6 +357,7 @@
<string name="row_text_icons_demo_title">Rows with Text and Icons Demo</string>
<!-- SelectableListsDemoScreen -->
+ <string name="radio_button_list_demo_title">Radio Button Lists Demo</string>
<string name="selectable_lists_demo_title">Selectable Lists Demo</string>
<string name="option_1_title">Option 1</string>
<string name="option_2_title">Option 2</string>
@@ -371,6 +373,9 @@
<string name="task_limit_reached_msg">Task limit reached\nGoing forward will force stop the app</string>
<string name="task_step_of_title">Task step %1$d of %2$d</string>
<string name="task_step_of_text">Click to go forward</string>
+
+ <!-- ToggleButtonDemoScreen -->
+ <string name="toggle_button_demo_title">Toggle Button Demo</string>
<string name="toggle_test_title">Toggle test</string>
<string name="toggle_test_text">Stateful changes are allowed</string>
<string name="toggle_test_first_toggle_title">Enable Toggle Test</string>
@@ -388,6 +393,8 @@
<string name="misc_templates_demos_title">Misc Templates Demos</string>
<string name="cal_api_level_prefix" translatable="false">CAL API Level: %d</string>
<string name="showcase_demos_title">Showcase Demos</string>
+ <string name="template_layouts_demo_title">Template Layout Demos</string>
+ <string name="grid_template_menu_demo_title">Grid Template Demos</string>
<!-- User Interactions Screen -->
<string name="voice_access_demo_title">Voice Access Demo Screen</string>
@@ -433,4 +440,12 @@
<string name="location_9_address" translatable="false">111 Waverly Way, Kirkland, WA 98033</string>
<string name="location_description_text_label">Text label</string>
<string name="location_phone_not_available" translatable="false">n/a</string>
+
+ <string name="parking_vs_driving_demo_title">Parking Vs Driving Demo</string>
+ <string name="latest_feature_details">Cluster Displays in cars!</string>
+ <string name="latest_feature_title">Latest Features</string>
+ <string name="loading_toggle_enabled">Loading enabled</string>
+ <string name="loading_toggle_disabled">Loading disabled</string>
+ <string name="loading_screen">Loading screen</string>
+ <string name="vector_toggle_details">Toggle to add/remove color</string>
</resources>
diff --git a/car/app/app/lint-baseline.xml b/car/app/app/lint-baseline.xml
index 6f805be..1c92b5a 100644
--- a/car/app/app/lint-baseline.xml
+++ b/car/app/app/lint-baseline.xml
@@ -327,7 +327,7 @@
<issue
id="WrongConstant"
- message="Must be one of: CarAppApiLevels.UNKNOWN, CarAppApiLevels.LEVEL_1, CarAppApiLevels.LEVEL_2, CarAppApiLevels.LEVEL_3, CarAppApiLevels.LEVEL_4, CarAppApiLevels.LEVEL_5"
+ message="Must be one of: CarAppApiLevels.UNKNOWN, CarAppApiLevels.LEVEL_1, CarAppApiLevels.LEVEL_2, CarAppApiLevels.LEVEL_3, CarAppApiLevels.LEVEL_4, CarAppApiLevels.LEVEL_5, CarAppApiLevels.LEVEL_6"
errorLine1=" mCarAppApiLevel = handshakeInfo.getHostCarAppApiLevel();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -370,4 +370,40 @@
file="src/main/java/androidx/car/app/navigation/model/PanModeDelegateImpl.java"/>
</issue>
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type java.util.List<androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+ errorLine1=" @ClimateProfileRequest.ClimateProfileFeature"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type java.util.List<androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+ errorLine1=" @ClimateProfileRequest.ClimateProfileFeature"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type java.util.List<androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+ errorLine1=" @ClimateProfileRequest.ClimateProfileFeature"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="This annotation does not apply for type androidx.car.app.model.Row.Builder; expected int or long"
+ errorLine1=" @IntRange(from = 0)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/car/app/model/Row.java"/>
+ </issue>
+
</issues>
diff --git a/paging/paging-common/lint-baseline.xml b/paging/paging-common/lint-baseline.xml
new file mode 100644
index 0000000..2bfe322
--- /dev/null
+++ b/paging/paging-common/lint-baseline.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/kotlin/androidx/paging/PageFetcher.kt"/>
+ </issue>
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:RestrictTo`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/kotlin/androidx/paging/PagedList.kt"/>
+ </issue>
+
+</issues>
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt
new file mode 100644
index 0000000..4f3f8e7
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2022 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.recyclerview.widget
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class AnchorTest : BaseRecyclerViewInstrumentationTest() {
+
+ @Test
+ fun noAnchoringWhenViewportNotFilled(): Unit = runBlocking(Dispatchers.Main) {
+ val mainAdapter = AnchorTestAdapter(0)
+ // This simulates a loading spinning added to the end via ConcatAdapter, such as Paging does
+ val suffixAdapter = AnchorTestAdapter(1)
+ val concatAdapter = ConcatAdapter(mainAdapter, suffixAdapter)
+
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ mRecyclerView = RecyclerView(context)
+ mRecyclerView.layoutParams = TestedFrameLayout.FullControlLayoutParams(100, 100)
+ mRecyclerView.adapter = concatAdapter
+ mRecyclerView.layoutManager = layoutManager
+ activity.container.addView(mRecyclerView)
+ mRecyclerView.awaitLayout()
+
+ assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+ assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(0)
+
+ mainAdapter.numItems = 20
+ mRecyclerView.awaitLayout()
+ // Before this was fixed, this would anchor to the "spinner" and end up at the
+ // bottom of the list (first visible item would be 11)
+ assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+ assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(9)
+ }
+
+ @Test
+ fun noAnchoringWhenFillingWrapContentRecyclerView(): Unit = runBlocking(Dispatchers.Main) {
+ val mainAdapter = AnchorTestAdapter(0)
+ // This simulates a loading spinning added to the end via ConcatAdapter, such as Paging does
+ val suffixAdapter = AnchorTestAdapter(1)
+ val concatAdapter = ConcatAdapter(mainAdapter, suffixAdapter)
+
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+
+ val frame = FrameLayout(context)
+ frame.layoutParams = TestedFrameLayout.FullControlLayoutParams(100, 100)
+ mRecyclerView = RecyclerView(context)
+ mRecyclerView.layoutParams =
+ FrameLayout.LayoutParams(100, ViewGroup.LayoutParams.WRAP_CONTENT)
+ mRecyclerView.adapter = concatAdapter
+ mRecyclerView.layoutManager = layoutManager
+ frame.addView(mRecyclerView)
+ activity.container.addView(frame)
+ mRecyclerView.awaitLayout()
+
+ assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+ assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(0)
+
+ mainAdapter.numItems = 20
+ mRecyclerView.awaitLayout()
+
+ // Before this was fixed, this would anchor to the "spinner" and end up at the
+ // bottom of the list (first visible item would be 11)
+ assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+ assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(9)
+ }
+
+ private class AnchorTestAdapter(length: Int) :
+ RecyclerView.Adapter<AnchorTestAdapter.ViewHolder>() {
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+
+ var numItems: Int = length
+ set(value) {
+ val old = field
+ val diff = value - old
+ if (diff < 0) {
+ TODO("Length reduction not supported")
+ }
+ field = value
+ notifyItemRangeInserted(old, diff)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val lp = RecyclerView.LayoutParams(10, 10)
+ val v = View(parent.context)
+ v.layoutParams = lp
+ return ViewHolder(v)
+ }
+
+ override fun getItemCount(): Int = numItems
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ }
+ }
+}
+
+private suspend fun View.awaitLayout() {
+ suspendCancellableCoroutine { continuation ->
+ val runnable = Runnable {
+ continuation.resume(Unit)
+ }
+ continuation.invokeOnCancellation {
+ this.removeCallbacks(runnable)
+ }
+ this.post(runnable)
+ }
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt
new file mode 100644
index 0000000..0fca345
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2021 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.recyclerview.widget
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.IllegalArgumentException
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+public class LinearLayoutManagerMeasureAnchorTest {
+
+ @Test
+ public fun testStackFromEndVertical() {
+ test(true, false, true)
+ }
+
+ @Test
+ public fun testReverseLayoutVertical() {
+ test(false, true, true)
+ }
+
+ @Test
+ public fun testStackFromEndHorizontal() {
+ test(true, false, false)
+ }
+
+ @Test
+ public fun testReverseLayoutHorizontal() {
+ test(false, true, false)
+ }
+
+ public fun test(reverseLayout: Boolean, stackFromEnd: Boolean, vertical: Boolean) {
+
+ if (!(reverseLayout xor stackFromEnd)) {
+ throw IllegalArgumentException("Must be reverseLayout xor stackFromEnd")
+ }
+
+ // Arrange
+
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val recyclerView = RecyclerView(context)
+ val orientation = if (vertical) RecyclerView.VERTICAL else RecyclerView.HORIZONTAL
+ recyclerView.layoutManager =
+ LinearLayoutManager(context, orientation, reverseLayout).apply {
+ this.stackFromEnd = stackFromEnd
+ }
+ recyclerView.adapter = TestAdapter(context, vertical)
+
+ // Act
+
+ recyclerView.measure(
+ View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST)
+ )
+ recyclerView.measure(
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST)
+ )
+ recyclerView.layout(0, 0, 100, 100)
+
+ // Assert
+
+ val view: View? =
+ (0 until recyclerView.childCount)
+ .map { recyclerView.getChildAt(it) }
+ .find { it.tag == if (reverseLayout) 0 else 9 }
+
+ assertThat(view).isNotNull()
+ if (vertical) {
+ assertThat(view!!.top).isEqualTo(0)
+ } else {
+ assertThat(view!!.left).isEqualTo(0)
+ }
+ }
+
+ internal class TestAdapter(val context: Context, val vertical: Boolean) :
+ RecyclerView.Adapter<TestViewHolder>() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
+ val view = View(context)
+ view.layoutParams = if (vertical) {
+ ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)
+ } else {
+ ViewGroup.LayoutParams(100, ViewGroup.LayoutParams.MATCH_PARENT)
+ }
+ return TestViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
+ holder.itemView.tag = position
+ }
+
+ override fun getItemCount(): Int {
+ return 10
+ }
+ }
+
+ internal class TestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt
new file mode 100644
index 0000000..3b42020
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2021 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.recyclerview.widget
+
+import android.content.Context
+import android.view.View
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@SmallTest
+public class RecyclerViewPreviousMeasuredDimensionsTest(
+ private val firstWidthMode: Int,
+ private val firstHeightMode: Int,
+ private val secondWidthMode: Int,
+ private val secondHeightMode: Int
+) {
+
+ public companion object {
+ @JvmStatic
+ @Parameterized.Parameters(
+ name = "firstWidthMode={0}, firstHeightMode={1}, " +
+ "secondWidthMode={2}, secondHeightMode={3}"
+ )
+ public fun data(): Collection<Array<Int>> {
+ val widthModes = listOf(
+ UNSPECIFIED,
+ EXACTLY,
+ AT_MOST
+ )
+
+ val data = ArrayList<Array<Int>>()
+ for (firstWidthMode in widthModes) {
+ for (firstHeightMode in widthModes) {
+ for (secondWidthMode in widthModes) {
+ for (secondHeightMode in widthModes) {
+ data.add(
+ listOf(
+ firstWidthMode,
+ firstHeightMode,
+ secondWidthMode,
+ secondHeightMode
+ ).toTypedArray()
+ )
+ }
+ }
+ }
+ }
+ return data
+ }
+ }
+
+ @Test
+ public fun previousMeasuredDimensionTest() {
+
+ // Arrange
+
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val recyclerView = RecyclerView(context)
+ recyclerView.layoutManager =
+ LinearLayoutManager(context).apply {
+ this.stackFromEnd = stackFromEnd
+ }
+ recyclerView.adapter = TestAdapter(context)
+
+ // Assert 0
+
+ assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(0)
+ assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(0)
+
+ // Act 1
+
+ recyclerView.measure(
+ View.MeasureSpec.makeMeasureSpec(100, firstWidthMode),
+ View.MeasureSpec.makeMeasureSpec(100, firstHeightMode)
+ )
+
+ // Assert 1
+
+ val expectedPreviousWidth1 = if (firstWidthMode == UNSPECIFIED) 300 else 100
+ val expectedPreviousHeight1 = if (firstHeightMode == UNSPECIFIED) 300 else 100
+ assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth1)
+ assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight1)
+
+ // Act 2
+
+ recyclerView.measure(
+ View.MeasureSpec.makeMeasureSpec(200, secondWidthMode),
+ View.MeasureSpec.makeMeasureSpec(200, secondHeightMode)
+ )
+
+ // Assert 2
+
+ val expectedPreviousWidth2 = if (secondWidthMode == UNSPECIFIED) 300 else 200
+ val expectedPreviousHeight2 = if (secondHeightMode == UNSPECIFIED) 300 else 200
+ assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth2)
+ assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight2)
+
+ // Act 3, layout should not have any affect on previous measured values.
+
+ recyclerView.layout(0, 0, 500, 500)
+
+ // Assert 3
+
+ assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth2)
+ assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight2)
+ }
+
+ internal class TestAdapter(val context: Context) :
+ RecyclerView.Adapter<TestViewHolder>() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
+ val view = View(context)
+ view.layoutParams = ViewGroup.LayoutParams(300, 300)
+ return TestViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
+ holder.itemView.tag = position
+ }
+
+ override fun getItemCount(): Int {
+ return 1
+ }
+ }
+
+ internal class TestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
index 22fd533..c7b7852 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
@@ -85,6 +85,30 @@
*/
private boolean mLastStackFromEnd;
+ /**
+ * Whether the last layout filled the entire viewport
+ *
+ * If the last layout did not fill the viewport, we should not attempt to calculate an
+ * anchoring based on the current children (other than if one is focused), because there
+ * isn't any scrolling that could have occurred that would indicate a position in the list
+ * that needs to be preserved - and in fact, trying to do so could produce the wrong result,
+ * such as the case of anchoring to a loading spinner at the end of the list.
+ */
+ private boolean mLastLayoutFilledViewport = false;
+
+ /**
+ * Whether the *current* layout filled the entire viewport
+ *
+ * This is used to populate mLastLayoutFilledViewport. It exists as a separate variable
+ * because we need to populate it at the correct moment, which is tricky due to the
+ * LayoutManager layout being called multiple times. We want to not set it in prelayout
+ * (because that's not the real layout), but we want to set it the *first* time that the
+ * actual layout is run, because for certain non-exact layout cases, there are two passes,
+ * with the second pass being provided an EXACTLY spec (when the actual spec was non-exact).
+ * This would otherwise incorrectly believe the viewport was filled, because it was provided
+ * just enough space to contain the content, and thus it would always fill the viewport.
+ */
+ private Boolean mThisLayoutFilledViewport = null;
/**
* Defines if layout should be calculated from end to start.
@@ -571,11 +595,26 @@
// resolve layout direction
resolveShouldLayoutReverse();
+ boolean layoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+
+ // The 2 booleans below are necessary because if we are laying out from the end, and the
+ // previous measured dimension is different from the new measured value, then any
+ // previously calculated anchor will be incorrect.
+ boolean reCalcAnchorDueToVertical = layoutFromEnd
+ && getOrientation() == RecyclerView.VERTICAL
+ && state.getPreviousMeasuredHeight() != getHeight();
+ boolean reCalcAnchorDueToHorizontal = layoutFromEnd
+ && getOrientation() == RecyclerView.HORIZONTAL
+ && state.getPreviousMeasuredWidth() != getWidth();
+
+ boolean reCalcAnchor = reCalcAnchorDueToVertical || reCalcAnchorDueToHorizontal
+ || mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null;
+
final View focused = getFocusedChild();
- if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
- || mPendingSavedState != null) {
+
+ if (!mAnchorInfo.mValid || reCalcAnchor) {
mAnchorInfo.reset();
- mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+ mAnchorInfo.mLayoutFromEnd = layoutFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
@@ -714,13 +753,17 @@
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
- if (mShouldReverseLayout ^ mStackFromEnd) {
+ if (layoutFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
+ if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
+ mThisLayoutFilledViewport =
+ (startOffset <= mOrientationHelper.getStartAfterPadding());
+ }
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
@@ -728,6 +771,10 @@
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
+ if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
+ mThisLayoutFilledViewport =
+ (endOffset >= mOrientationHelper.getEndAfterPadding());
+ }
}
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
@@ -749,6 +796,8 @@
mPendingSavedState = null; // we don't need this anymore
mPendingScrollPosition = RecyclerView.NO_POSITION;
mPendingScrollPositionOffset = INVALID_OFFSET;
+ mLastLayoutFilledViewport = mThisLayoutFilledViewport != null && mThisLayoutFilledViewport;
+ mThisLayoutFilledViewport = null;
mAnchorInfo.reset();
}
@@ -858,11 +907,19 @@
if (getChildCount() == 0) {
return false;
}
+
final View focused = getFocusedChild();
if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
return true;
}
+
+ // If we did not fill the layout, don't anchor. This prevents, for example,
+ // anchoring to the bottom of the list when there is a loading indicator.
+ if (!mLastLayoutFilledViewport) {
+ return false;
+ }
+
if (mLastStackFromEnd != mStackFromEnd) {
return false;
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index df4de2d..866b4c4 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -2226,7 +2226,7 @@
/**
* <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
- * range. This value is used to compute the length of the thumb within the scrollbar's track.
+ * range. This value is used to compute the position of the thumb within the scrollbar's track.
* </p>
*
* <p>The range is expressed in arbitrary units that must be the same as the units used by
@@ -2287,7 +2287,7 @@
* {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
* LayoutManager.</p>
*
- * @return The total horizontal range represented by the vertical scrollbar
+ * @return The total horizontal range represented by the horizontal scrollbar
* @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
*/
@Override
@@ -2300,7 +2300,7 @@
/**
* <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
- * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+ * This value is used to compute the position of the thumb within the scrollbar's track. </p>
*
* <p>The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
@@ -3946,6 +3946,12 @@
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
+ internalOnMeasure(widthSpec, heightSpec);
+ mState.mPreviousMeasuredWidth = this.getMeasuredWidth();
+ mState.mPreviousMeasuredHeight = this.getMeasuredHeight();
+ }
+
+ private void internalOnMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
@@ -10893,7 +10899,7 @@
* <p>Default implementation returns 0.</p>
*
* @param state Current State of RecyclerView where you can find total item count
- * @return The total horizontal range represented by the vertical scrollbar
+ * @return The total horizontal range represented by the horizontal scrollbar
* @see RecyclerView#computeHorizontalScrollRange()
*/
public int computeHorizontalScrollRange(@NonNull State state) {
@@ -13296,6 +13302,42 @@
boolean mRunPredictiveAnimations = false;
/**
+ * The values in these fields reflect the values passed to
+ * {@link RecyclerView#setMeasuredDimension(int, int)} and are set just before
+ * {@link RecyclerView#onMeasure(int, int)} is completed. They are intended to be used to
+ * during onMeasure(int, int) to know how the RecyclerView was measured during previous
+ * calls to onMeasure(int, int).
+ */
+ int mPreviousMeasuredWidth = 0;
+ int mPreviousMeasuredHeight = 0;
+
+ // TODO(b/181991552): Make this public after 1.2.0 stable.
+ /**
+ * Returns the previously measured width of the {@link RecyclerView} that was most
+ * recently set via {@link RecyclerView#setMeasuredDimension(int, int)} during the most
+ * recent call to {@link RecyclerView#onMeasure(int, int)}. This is intended to be used
+ * during {@link LayoutManager#onLayoutChildren(Recycler, State)} when
+ * {@link State#isMeasuring()} is {@code true} in order to understand how the current
+ * measure specs compare to the result of any previous measurement.
+ */
+ int getPreviousMeasuredWidth() {
+ return mPreviousMeasuredWidth;
+ }
+
+ // TODO(b/181991552): Make this public after 1.2.0 stable.
+ /**
+ * Returns the previously measured height of the {@link RecyclerView} that was most
+ * recently set via {@link RecyclerView#setMeasuredDimension(int, int)} during the most
+ * recent call to {@link RecyclerView#onMeasure(int, int)}. This is intended to be used
+ * during {@link LayoutManager#onLayoutChildren(Recycler, State)} when
+ * {@link State#isMeasuring()} is {@code true} in order to understand how the current
+ * measure specs compare to the result of any previous measurement.
+ */
+ int getPreviousMeasuredHeight() {
+ return mPreviousMeasuredHeight;
+ }
+
+ /**
* This data is saved before a layout calculation happens. After the layout is finished,
* if the previously focused view has been replaced with another view for the same item, we
* move the focus to the new item automatically.
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 63c2de1..ae1c0fb 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -83,10 +83,15 @@
}
appendLine()
}
- appendLine("Generated files:")
- generatedSources.forEach {
- appendLine(it.relativePath)
+ if (generatedSources.isEmpty()) {
+ appendLine("Generated files: NONE")
+ } else {
+ appendLine("Generated files:")
+ generatedSources.forEach {
+ appendLine(it.relativePath)
+ }
}
+ appendLine()
appendLine("RAW OUTPUT:")
appendLine(rawOutput())
}
@@ -414,7 +419,7 @@
if (processingException != null) {
// processor has an error which we want to throw but we also want the subject, hence
// we wrap it
- throw createProcessorAssertionError(
+ throw CompilationAssertionError(
compilationResult = compilationResult,
realError = processingException
)
@@ -466,13 +471,15 @@
}
/**
- * Helper method to create an exception that does not include the stack trace from the test
- * infra, instead, it just reports the stack trace of the actual error with added log.
+ * Helper error that does not include the stack trace from the test infra, instead, it just
+ * reports the stack trace of the actual error with added log.
*/
- private fun createProcessorAssertionError(
- compilationResult: CompilationResult,
- realError: Throwable
- ) = object : AssertionError("processor did throw an error\n$compilationResult", realError) {
+ private class CompilationAssertionError(
+ val compilationResult: CompilationResult,
+ val realError: Throwable
+ ) : AssertionError(
+ "Processor did throw an error.\n$compilationResult", realError
+ ) {
override fun fillInStackTrace(): Throwable {
return realError
}
@@ -509,7 +516,7 @@
diagnostics = diagnostics
) {
override fun rawOutput(): String {
- return delegate.diagnostics().joinToString {
+ return delegate.diagnostics().joinToString(separator = System.lineSeparator()) {
it.toString()
}
}
@@ -532,7 +539,7 @@
override fun rawOutput(): String {
return delegate.diagnostics.flatMap {
it.value
- }.joinToString {
+ }.joinToString(separator = System.lineSeparator()) {
it.toString()
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
index 77be512..a425abc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
@@ -17,9 +17,18 @@
package androidx.room.compiler.codegen
import androidx.room.compiler.processing.XNullability
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.KTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.toKClassName
+
+typealias JCodeBlock = com.squareup.javapoet.CodeBlock
+typealias JCodeBlockBuilder = com.squareup.javapoet.CodeBlock.Builder
+typealias JAnnotationSpecBuilder = com.squareup.javapoet.AnnotationSpec.Builder
+typealias JTypeSpecBuilder = com.squareup.javapoet.TypeSpec.Builder
+typealias KCodeBlock = com.squareup.kotlinpoet.CodeBlock
+typealias KCodeBlockBuilder = com.squareup.kotlinpoet.CodeBlock.Builder
+typealias KAnnotationSpecBuilder = com.squareup.kotlinpoet.AnnotationSpec.Builder
+typealias KTypeSpecBuilder = com.squareup.kotlinpoet.TypeSpec.Builder
+typealias JArrayTypeName = com.squareup.javapoet.ArrayTypeName
// TODO(b/127483380): Recycle to room-compiler?
val L = "\$L"
@@ -28,9 +37,5 @@
val S = "\$S"
val W = "\$W"
-internal fun JTypeName.toKTypeName(nullability: XNullability): KTypeName = this.toKTypeName().let {
- when (nullability) {
- XNullability.NULLABLE, XNullability.UNKNOWN -> it.copy(nullable = true)
- XNullability.NONNULL -> it.copy(nullable = false)
- }
-}
\ No newline at end of file
+// TODO(b/247247366): Temporary migration API, delete me plz!
+fun JClassName.toXClassName() = XClassName(this, this.toKClassName(), XNullability.NONNULL)
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
index e6617fb..cafc0f0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
@@ -18,8 +18,6 @@
import androidx.room.compiler.codegen.java.JavaAnnotationSpec
import androidx.room.compiler.codegen.kotlin.KotlinAnnotationSpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.toKClassName
interface XAnnotationSpec : TargetLanguage {
@@ -41,13 +39,13 @@
}
companion object {
- fun builder(language: CodeLanguage, className: JClassName): Builder {
+ fun builder(language: CodeLanguage, className: XClassName): Builder {
return when (language) {
CodeLanguage.JAVA -> JavaAnnotationSpec.Builder(
- com.squareup.javapoet.AnnotationSpec.builder(className)
+ com.squareup.javapoet.AnnotationSpec.builder(className.java)
)
CodeLanguage.KOTLIN -> KotlinAnnotationSpec.Builder(
- com.squareup.kotlinpoet.AnnotationSpec.builder(className.toKClassName())
+ com.squareup.kotlinpoet.AnnotationSpec.builder(className.kotlin)
)
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
index 1e7da00..4b16c2b6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
@@ -18,7 +18,6 @@
import androidx.room.compiler.codegen.java.JavaCodeBlock
import androidx.room.compiler.codegen.kotlin.KotlinCodeBlock
-import com.squareup.kotlinpoet.javapoet.JTypeName
interface XCodeBlock : TargetLanguage {
@@ -32,7 +31,7 @@
fun addLocalVariable(
name: String,
- type: JTypeName,
+ type: XTypeName,
isMutable: Boolean = false,
assignExpr: XCodeBlock
): Builder
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
index ff6be2c..898c8d1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
@@ -20,20 +20,17 @@
import androidx.room.compiler.codegen.java.toJavaVisibilityModifier
import androidx.room.compiler.codegen.kotlin.KotlinFunSpec
import androidx.room.compiler.codegen.kotlin.toKotlinVisibilityModifier
-import androidx.room.compiler.processing.XNullability
import com.squareup.javapoet.MethodSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.javapoet.JTypeName
interface XFunSpec : TargetLanguage {
interface Builder : TargetLanguage {
// TODO(b/247247442): Maybe make a XParameterSpec ?
fun addParameter(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
annotations: List<XAnnotationSpec> = emptyList()
): Builder
@@ -43,10 +40,7 @@
fun callSuperConstructor(vararg args: XCodeBlock): Builder
- fun returns(
- typeName: JTypeName,
- nullability: XNullability,
- ): Builder
+ fun returns(typeName: XTypeName): Builder
fun build(): XFunSpec
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
new file mode 100644
index 0000000..e792548
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 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.room.compiler.codegen
+
+import androidx.room.compiler.processing.XNullability
+import com.squareup.kotlinpoet.DelicateKotlinPoetApi
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KClassName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import kotlin.reflect.KClass
+
+/**
+ * Represents a type name in Java and Kotlin's type system.
+ *
+ * It simply contains a [com.squareup.javapoet.TypeName] and a [com.squareup.kotlinpoet.TypeName].
+ * If the name comes from xprocessing APIs then the KotlinPoet name will default to 'Unavailable'
+ * if the processing backend is not KSP.
+ *
+ * @see [androidx.room.compiler.processing.XType.asTypeName]
+ */
+open class XTypeName protected constructor(
+ internal open val java: JTypeName,
+ internal open val kotlin: KTypeName,
+ internal val nullability: XNullability
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is XTypeName) return false
+ if (java != other.java) return false
+ if (kotlin != other.kotlin) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = java.hashCode()
+ result = 31 * result + kotlin.hashCode()
+ return result
+ }
+
+ override fun toString() = buildString {
+ append("XTypeName[")
+ append(java)
+ append(" / ")
+ if (kotlin != UNAVAILABLE_KTYPE_NAME) {
+ append(kotlin)
+ } else {
+ append("UNAVAILABLE")
+ }
+ append("]")
+ }
+
+ companion object {
+ /**
+ * The default [KTypeName] returned by xprocessing APIs when the backend is not KSP.
+ */
+ internal val UNAVAILABLE_KTYPE_NAME =
+ KClassName("androidx.room.compiler.codegen", "Unavailable")
+
+ operator fun invoke(
+ java: JTypeName,
+ kotlin: KTypeName,
+ nullability: XNullability
+ ): XTypeName {
+ return XTypeName(java, kotlin, nullability)
+ }
+ }
+}
+
+/**
+ * Represents a fully-qualified class name.
+ *
+ * It simply contains a [com.squareup.javapoet.ClassName] and a [com.squareup.kotlinpoet.ClassName].
+ *
+ * @see [androidx.room.compiler.processing.XTypeElement.asClassName]
+ */
+class XClassName internal constructor(
+ override val java: JClassName,
+ override val kotlin: KClassName,
+ nullability: XNullability
+) : XTypeName(java, kotlin, nullability) {
+
+ // TODO(b/248000692): Using the JClassName as source of truth. This is wrong since we need to
+ // handle Kotlin interop types for KotlinPoet, i.e. java.lang.String to kotlin.String.
+ // But a decision has to be made...
+ val packageName: String = java.packageName()
+ val simpleNames: List<String> = java.simpleNames()
+
+ fun copy(nullable: Boolean): XClassName {
+ return XClassName(
+ java = java,
+ kotlin = kotlin.copy(nullable = nullable) as KClassName,
+ nullability = if (nullable) XNullability.NULLABLE else XNullability.NONNULL
+ )
+ }
+
+ companion object {
+ fun get(
+ packageName: String,
+ vararg names: String
+ ): XClassName {
+ return XClassName(
+ java = JClassName.get(packageName, names.first(), *names.drop(1).toTypedArray()),
+ kotlin = KClassName(packageName, *names),
+ nullability = XNullability.NONNULL
+ )
+ }
+ }
+}
+
+@OptIn(DelicateKotlinPoetApi::class)
+fun Class<*>.asClassName(): XClassName {
+ return XClassName(
+ java = JClassName.get(this),
+ kotlin = this.asClassName(),
+ nullability = XNullability.NONNULL
+ )
+}
+
+fun KClass<*>.asClassName(): XClassName {
+ return XClassName(
+ java = JClassName.get(this.java),
+ kotlin = this.asClassName(),
+ nullability = XNullability.NONNULL
+ )
+}
+
+fun XTypeName.toJavaPoet() = this.java
+fun XClassName.toJavaPoet() = this.java
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
index e1799c5..506d1c6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
@@ -19,25 +19,20 @@
import androidx.room.compiler.codegen.java.JavaTypeSpec
import androidx.room.compiler.codegen.kotlin.KotlinTypeSpec
import androidx.room.compiler.processing.XElement
-import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.addOriginatingElement
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKClassName
interface XTypeSpec : TargetLanguage {
- val className: JClassName
+ val className: XClassName
interface Builder : TargetLanguage {
- fun superclass(typeName: JTypeName): Builder
+ fun superclass(typeName: XTypeName): Builder
fun addAnnotation(annotation: XAnnotationSpec)
// TODO(b/247241418): Maybe make a XPropertySpec ?
fun addProperty(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
visibility: VisibilityModifier,
isMutable: Boolean = false,
initExpr: XCodeBlock? = null,
@@ -67,15 +62,15 @@
}
companion object {
- fun classBuilder(language: CodeLanguage, className: JClassName): Builder {
+ fun classBuilder(language: CodeLanguage, className: XClassName): Builder {
return when (language) {
CodeLanguage.JAVA -> JavaTypeSpec.Builder(
className = className,
- actual = com.squareup.javapoet.TypeSpec.classBuilder(className)
+ actual = com.squareup.javapoet.TypeSpec.classBuilder(className.java)
)
CodeLanguage.KOTLIN -> KotlinTypeSpec.Builder(
className = className,
- actual = com.squareup.kotlinpoet.TypeSpec.classBuilder(className.toKClassName())
+ actual = com.squareup.kotlinpoet.TypeSpec.classBuilder(className.kotlin)
)
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
similarity index 94%
rename from room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt
rename to room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
index 465ea86..b4637ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.room.compiler.codegen
+package androidx.room.compiler.codegen.java
import com.squareup.javapoet.ClassName
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
index 06e915a..2a3e25f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
@@ -16,15 +16,17 @@
package androidx.room.compiler.codegen.java
+import androidx.room.compiler.codegen.JAnnotationSpecBuilder
import androidx.room.compiler.codegen.XAnnotationSpec
import androidx.room.compiler.codegen.XCodeBlock
+import com.squareup.kotlinpoet.javapoet.JAnnotationSpec
internal class JavaAnnotationSpec(
- internal val actual: com.squareup.javapoet.AnnotationSpec
+ internal val actual: JAnnotationSpec
) : JavaLang(), XAnnotationSpec {
internal class Builder(
- internal val actual: com.squareup.javapoet.AnnotationSpec.Builder
+ internal val actual: JAnnotationSpecBuilder
) : JavaLang(), XAnnotationSpec.Builder {
override fun addMember(name: String, code: XCodeBlock) = apply {
require(code is JavaCodeBlock)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
index 6bc983b..a5e9cff 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
@@ -17,18 +17,19 @@
package androidx.room.compiler.codegen.java
import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.JCodeBlock
import androidx.room.compiler.codegen.TargetLanguage
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
internal class JavaCodeBlock(
- internal val actual: com.squareup.javapoet.CodeBlock
+ internal val actual: JCodeBlock
) : JavaLang(), XCodeBlock {
internal class Builder : JavaLang(), XCodeBlock.Builder {
- internal val actual = com.squareup.javapoet.CodeBlock.builder()
+ internal val actual = JCodeBlock.builder()
override fun add(code: XCodeBlock) = apply {
require(code is JavaCodeBlock)
@@ -49,7 +50,7 @@
override fun addLocalVariable(
name: String,
- type: JTypeName,
+ type: XTypeName,
isMutable: Boolean,
assignExpr: XCodeBlock
) = apply {
@@ -78,6 +79,7 @@
check(arg.language == CodeLanguage.JAVA) { "$arg is not JavaCode" }
}
when (arg) {
+ is XTypeName -> arg.java
is XTypeSpec -> (arg as JavaTypeSpec).actual
is XFunSpec -> (arg as JavaFunSpec).actual
is XCodeBlock -> (arg as JavaCodeBlock).actual
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
index ae0bc2d..a8113df 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
@@ -17,12 +17,11 @@
package androidx.room.compiler.codegen.java
import androidx.room.compiler.codegen.L
-import androidx.room.compiler.codegen.NONNULL_ANNOTATION
-import androidx.room.compiler.codegen.NULLABLE_ANNOTATION
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XAnnotationSpec
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.KnownTypeNames.KOTLIN_UNIT
import androidx.room.compiler.processing.XNullability
import com.squareup.javapoet.CodeBlock
@@ -36,7 +35,7 @@
) : JavaLang(), XFunSpec {
internal class Builder(
- internal val actual: com.squareup.javapoet.MethodSpec.Builder
+ internal val actual: MethodSpec.Builder
) : JavaLang(), XFunSpec.Builder {
override fun addCode(code: XCodeBlock) = apply {
@@ -45,17 +44,16 @@
}
override fun addParameter(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
annotations: List<XAnnotationSpec>
) = apply {
actual.addParameter(
- ParameterSpec.builder(typeName, name, Modifier.FINAL)
+ ParameterSpec.builder(typeName.java, name, Modifier.FINAL)
.apply {
- if (nullability == XNullability.NULLABLE) {
+ if (typeName.nullability == XNullability.NULLABLE) {
addAnnotation(NULLABLE_ANNOTATION)
- } else if (nullability == XNullability.NONNULL) {
+ } else if (typeName.nullability == XNullability.NONNULL) {
addAnnotation(NONNULL_ANNOTATION)
}
}.build()
@@ -76,19 +74,19 @@
)
}
- override fun returns(typeName: JTypeName, nullability: XNullability) = apply {
- if (typeName == com.squareup.javapoet.TypeName.VOID || typeName == KOTLIN_UNIT) {
+ override fun returns(typeName: XTypeName) = apply {
+ if (typeName.java == JTypeName.VOID || typeName.java == KOTLIN_UNIT) {
return@apply
}
// TODO(b/247242374) Add nullability annotations for non-private methods
if (!actual.modifiers.contains(Modifier.PRIVATE)) {
- if (nullability == XNullability.NULLABLE) {
+ if (typeName.nullability == XNullability.NULLABLE) {
actual.addAnnotation(NULLABLE_ANNOTATION)
- } else if (nullability == XNullability.NONNULL) {
+ } else if (typeName.nullability == XNullability.NONNULL) {
actual.addAnnotation(NONNULL_ANNOTATION)
}
}
- actual.returns(typeName)
+ actual.returns(typeName.java)
}
override fun build() = JavaFunSpec(actual.build())
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
index 0dd5016..952ef06 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
@@ -16,30 +16,30 @@
package androidx.room.compiler.codegen.java
-import androidx.room.compiler.codegen.NONNULL_ANNOTATION
-import androidx.room.compiler.codegen.NULLABLE_ANNOTATION
+import androidx.room.compiler.codegen.JTypeSpecBuilder
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XAnnotationSpec
+import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.processing.XNullability
import com.squareup.javapoet.FieldSpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeSpec
import javax.lang.model.element.Modifier
internal class JavaTypeSpec(
- override val className: JClassName,
- internal val actual: com.squareup.javapoet.TypeSpec
+ override val className: XClassName,
+ internal val actual: JTypeSpec
) : JavaLang(), XTypeSpec {
internal class Builder(
- private val className: JClassName,
- internal val actual: com.squareup.javapoet.TypeSpec.Builder
+ private val className: XClassName,
+ internal val actual: JTypeSpecBuilder
) : JavaLang(), XTypeSpec.Builder {
- override fun superclass(typeName: JTypeName) = apply {
- actual.superclass(typeName)
+ override fun superclass(typeName: XTypeName) = apply {
+ actual.superclass(typeName.java)
}
override fun addAnnotation(annotation: XAnnotationSpec) {
@@ -48,22 +48,21 @@
}
override fun addProperty(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
visibility: VisibilityModifier,
isMutable: Boolean,
initExpr: XCodeBlock?,
annotations: List<XAnnotationSpec>
) = apply {
actual.addField(
- FieldSpec.builder(typeName, name).apply {
+ FieldSpec.builder(typeName.java, name).apply {
val visibilityModifier = visibility.toJavaVisibilityModifier()
// TODO(b/247242374) Add nullability annotations for non-private fields
if (visibilityModifier != Modifier.PRIVATE) {
- if (nullability == XNullability.NULLABLE) {
+ if (typeName.nullability == XNullability.NULLABLE) {
addAnnotation(NULLABLE_ANNOTATION)
- } else if (nullability == XNullability.NONNULL) {
+ } else if (typeName.nullability == XNullability.NONNULL) {
addAnnotation(NONNULL_ANNOTATION)
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
index cdec2ad2..50222ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
@@ -16,16 +16,18 @@
package androidx.room.compiler.codegen.kotlin
+import androidx.room.compiler.codegen.KAnnotationSpecBuilder
import androidx.room.compiler.codegen.XAnnotationSpec
import androidx.room.compiler.codegen.XCodeBlock
import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.javapoet.KAnnotationSpec
internal class KotlinAnnotationSpec(
- internal val actual: com.squareup.kotlinpoet.AnnotationSpec
+ internal val actual: KAnnotationSpec
) : KotlinLang(), XAnnotationSpec {
internal class Builder(
- internal val actual: com.squareup.kotlinpoet.AnnotationSpec.Builder
+ internal val actual: KAnnotationSpecBuilder
) : KotlinLang(), XAnnotationSpec.Builder {
override fun addMember(name: String, code: XCodeBlock) = apply {
require(code is KotlinCodeBlock)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
index 1ccc851..ede67d2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
@@ -17,20 +17,21 @@
package androidx.room.compiler.codegen.kotlin
import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.KCodeBlock
+import androidx.room.compiler.codegen.KCodeBlockBuilder
import androidx.room.compiler.codegen.TargetLanguage
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
internal class KotlinCodeBlock(
- internal val actual: com.squareup.kotlinpoet.CodeBlock
+ internal val actual: KCodeBlock
) : KotlinLang(), XCodeBlock {
internal class Builder : KotlinLang(), XCodeBlock.Builder {
- internal val actual = com.squareup.kotlinpoet.CodeBlock.Builder()
+ internal val actual = KCodeBlockBuilder()
override fun add(code: XCodeBlock) = apply {
require(code is KotlinCodeBlock)
@@ -51,7 +52,7 @@
override fun addLocalVariable(
name: String,
- type: JTypeName,
+ type: XTypeName,
isMutable: Boolean,
assignExpr: XCodeBlock
) = apply {
@@ -59,7 +60,7 @@
val varOrVal = if (isMutable) "var" else "val"
actual.addStatement(
"$varOrVal %L: %T = %L",
- type.toKTypeName(),
+ type.kotlin,
name,
assignExpr.actual
)
@@ -74,13 +75,11 @@
private fun processArgs(args: Array<out Any?>): Array<Any?> {
return Array(args.size) { index ->
val arg = args[index]
- if (arg is JTypeName) {
- return@Array arg.toKTypeName()
- }
if (arg is TargetLanguage) {
check(arg.language == CodeLanguage.KOTLIN) { "$arg is not KotlinCode" }
}
when (arg) {
+ is XTypeName -> arg.kotlin
is XTypeSpec -> (arg as KotlinTypeSpec).actual
is XFunSpec -> (arg as KotlinFunSpec).actual
is XCodeBlock -> (arg as KotlinCodeBlock).actual
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
index 0faf0ab..fdcb73f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
@@ -20,20 +20,18 @@
import androidx.room.compiler.codegen.XAnnotationSpec
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
-import androidx.room.compiler.codegen.toKTypeName
-import androidx.room.compiler.processing.KnownTypeNames.KOTLIN_UNIT
-import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.codegen.XTypeName
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.UNIT
internal class KotlinFunSpec(
internal val actual: FunSpec
) : KotlinLang(), XFunSpec {
internal class Builder(
- internal val actual: com.squareup.kotlinpoet.FunSpec.Builder
+ internal val actual: FunSpec.Builder
) : KotlinLang(), XFunSpec.Builder {
override fun addCode(code: XCodeBlock) = apply {
@@ -42,13 +40,12 @@
}
override fun addParameter(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
annotations: List<XAnnotationSpec>
) = apply {
actual.addParameter(
- ParameterSpec.builder(name, typeName.toKTypeName(nullability)).apply {
+ ParameterSpec.builder(name, typeName.kotlin).apply {
// TODO(b/247247439): Add other annotations
}.build()
)
@@ -63,11 +60,11 @@
)
}
- override fun returns(typeName: JTypeName, nullability: XNullability) = apply {
- if (typeName == com.squareup.javapoet.TypeName.VOID || typeName == KOTLIN_UNIT) {
+ override fun returns(typeName: XTypeName) = apply {
+ if (typeName.kotlin == UNIT) {
return@apply
}
- actual.returns(typeName.toKTypeName(nullability))
+ actual.returns(typeName.kotlin)
}
override fun build() = KotlinFunSpec(actual.build())
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
index 4223657..da898fc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
@@ -16,29 +16,28 @@
package androidx.room.compiler.codegen.kotlin
+import androidx.room.compiler.codegen.KTypeSpecBuilder
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XAnnotationSpec
+import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.toKTypeName
-import androidx.room.compiler.processing.XNullability
import com.squareup.kotlinpoet.PropertySpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeSpec
internal class KotlinTypeSpec(
- override val className: JClassName,
- internal val actual: com.squareup.kotlinpoet.TypeSpec
+ override val className: XClassName,
+ internal val actual: KTypeSpec
) : KotlinLang(), XTypeSpec {
internal class Builder(
- private val className: JClassName,
- internal val actual: com.squareup.kotlinpoet.TypeSpec.Builder
+ private val className: XClassName,
+ internal val actual: KTypeSpecBuilder
) : KotlinLang(), XTypeSpec.Builder {
- override fun superclass(typeName: JTypeName) = apply {
- actual.superclass(typeName.toKTypeName())
+ override fun superclass(typeName: XTypeName) = apply {
+ actual.superclass(typeName.kotlin)
}
override fun addAnnotation(annotation: XAnnotationSpec) {
@@ -47,16 +46,15 @@
}
override fun addProperty(
- typeName: JTypeName,
+ typeName: XTypeName,
name: String,
- nullability: XNullability,
visibility: VisibilityModifier,
isMutable: Boolean,
initExpr: XCodeBlock?,
annotations: List<XAnnotationSpec>
) = apply {
actual.addProperty(
- PropertySpec.builder(name, typeName.toKTypeName(nullability)).apply {
+ PropertySpec.builder(name, typeName.kotlin).apply {
mutable(isMutable)
addModifiers(visibility.toKotlinVisibilityModifier())
initExpr?.let {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index 95b43d7..ac25598 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -15,14 +15,14 @@
*/
package androidx.room.compiler.processing
-import java.lang.Character.isISOControl
import com.squareup.javapoet.AnnotationSpec
-import com.squareup.javapoet.ClassName
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
+import com.squareup.kotlinpoet.javapoet.JClassName
+import java.lang.Character.isISOControl
import javax.lang.model.SourceVersion
import javax.lang.model.element.Modifier
import javax.lang.model.type.TypeKind
@@ -37,7 +37,8 @@
*
* We should still strive to avoid these cases, maybe turn it to an error in tests.
*/
-private val NONE_TYPE_NAME = ClassName.get("androidx.room.compiler.processing.error", "NotAType")
+internal val JAVA_NONE_TYPE_NAME: JClassName =
+ JClassName.get("androidx.room.compiler.processing.error", "NotAType")
fun XAnnotation.toAnnotationSpec(): AnnotationSpec {
val builder = AnnotationSpec.builder(className)
@@ -82,7 +83,7 @@
}
internal fun TypeMirror.safeTypeName(): TypeName = if (kind == TypeKind.NONE) {
- NONE_TYPE_NAME
+ JAVA_NONE_TYPE_NAME
} else {
TypeName.get(this)
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
index 02affec..a02f25d 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
@@ -17,6 +17,10 @@
package androidx.room.compiler.processing
import com.squareup.kotlinpoet.OriginatingElementsHolder
+import com.squareup.kotlinpoet.javapoet.KClassName
+
+internal val KOTLIN_NONE_TYPE_NAME: KClassName =
+ KClassName("androidx.room.compiler.processing.error", "NotAType")
/**
* Adds the given element as an originating element for compilation.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
index d5cc7f0..227ba42 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
@@ -53,17 +53,17 @@
}
fun XTypeSpec.writeTo(generator: XFiler, mode: XFiler.Mode = XFiler.Mode.Isolating) {
- require(this.className.simpleNames().size == 1) { "XTypeSpec must be a top-level class." }
+ require(this.className.simpleNames.size == 1) { "XTypeSpec must be a top-level class." }
when (this.language) {
CodeLanguage.JAVA -> {
check(this is JavaTypeSpec)
- JavaFile.builder(this.className.packageName(), this.actual)
+ JavaFile.builder(this.className.packageName, this.actual)
.build()
.writeTo(generator, mode)
}
CodeLanguage.KOTLIN -> {
check(this is KotlinTypeSpec)
- FileSpec.builder(this.className.packageName(), this.className.simpleName())
+ FileSpec.builder(this.className.packageName, this.className.simpleNames.single())
.addType(this.actual)
.build()
.writeTo(generator, mode)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index 31f9522..5636707 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing
+import androidx.room.compiler.codegen.XTypeName
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import kotlin.contracts.contract
@@ -31,9 +32,22 @@
/**
* The Javapoet [TypeName] representation of the type
*/
+ // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
+ // @Deprecated(
+ // message = "Use asTypeName().toJavaPoet() to be clear the name is for JavaPoet.",
+ // replaceWith = ReplaceWith(
+ // expression = "asTypeName().toJavaPoet()",
+ // imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+ // )
+ // )
val typeName: TypeName
/**
+ * Gets the [XTypeName] representing the type.
+ */
+ fun asTypeName(): XTypeName
+
+ /**
* Returns the rawType of this type. (e.g. `List<String>` to `List`.
*/
val rawType: XRawType
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index aa9d882..3b49f682 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing
+import androidx.room.compiler.codegen.XClassName
import com.squareup.javapoet.ClassName
interface XTypeElement : XHasModifiers, XParameterizable, XElement, XMemberContainer {
@@ -64,9 +65,22 @@
/**
* Javapoet [ClassName] of the type.
*/
+ // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
+ // @Deprecated(
+ // message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+ // replaceWith = ReplaceWith(
+ // expression = "asClassName().toJavaPoet()",
+ // imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+ // )
+ // )
override val className: ClassName
/**
+ * Gets the [XClassName] of the type element.
+ */
+ fun asClassName(): XClassName
+
+ /**
* The [XTypeElement] that contains this [XTypeElement] if it is an inner class/interface.
*/
val enclosingTypeElement: XTypeElement?
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
index 66c25ae..b9c3ce7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
@@ -16,6 +16,8 @@
package androidx.room.compiler.processing.javac
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XArrayType
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
@@ -72,6 +74,16 @@
arrayOf(typeMirror)
}
+ private val xTypeName: XTypeName by lazy {
+ XTypeName(
+ java = JArrayTypeName.get(typeMirror),
+ kotlin = XTypeName.UNAVAILABLE_KTYPE_NAME,
+ nullability = knownComponentNullability ?: XNullability.UNKNOWN,
+ )
+ }
+
+ override fun asTypeName() = xTypeName
+
override val typeArguments: List<XType>
get() = emptyList()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 3526dc96..1735ad4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -16,16 +16,16 @@
package androidx.room.compiler.processing.javac
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.javac.kotlin.KmType
import androidx.room.compiler.processing.javac.kotlin.KotlinMetadataElement
-import androidx.room.compiler.processing.ksp.ERROR_TYPE_NAME
+import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
import androidx.room.compiler.processing.safeTypeName
import com.google.auto.common.MoreTypes
-import java.lang.IllegalStateException
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass
@@ -68,13 +68,23 @@
override fun isError(): Boolean {
return typeMirror.kind == TypeKind.ERROR ||
// https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
- (kotlinType != null && typeName == ERROR_TYPE_NAME)
+ (kotlinType != null && typeName == ERROR_JTYPE_NAME)
}
override val typeName by lazy {
- typeMirror.safeTypeName()
+ xTypeName.java
}
+ private val xTypeName: XTypeName by lazy {
+ XTypeName(
+ typeMirror.safeTypeName(),
+ XTypeName.UNAVAILABLE_KTYPE_NAME,
+ nullability
+ )
+ }
+
+ override fun asTypeName() = xTypeName
+
override fun equals(other: Any?): Boolean {
return XEquality.equals(this, other)
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 0eb5474..403d6fb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -16,12 +16,15 @@
package androidx.room.compiler.processing.javac
+import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XEnumEntry
import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XHasModifiers
-import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.XTypeParameterElement
@@ -34,6 +37,7 @@
import com.google.auto.common.MoreTypes
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind
@@ -60,9 +64,18 @@
}
override val className: ClassName by lazy {
- ClassName.get(element)
+ xClassName.java
}
+ private val xClassName: XClassName by lazy {
+ XClassName(
+ JClassName.get(element),
+ XTypeName.UNAVAILABLE_KTYPE_NAME,
+ XNullability.NONNULL
+ )
+ }
+ override fun asClassName() = xClassName
+
override val enclosingElement: XMemberContainer? by lazy {
enclosingTypeElement
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
index ac380bf..c6f3ec1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
@@ -19,17 +19,23 @@
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.tryBox
import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
internal class DefaultKspType(
env: KspProcessingEnv,
ksType: KSType,
jvmTypeResolver: KspJvmTypeResolver?
) : KspType(env, ksType, jvmTypeResolver) {
- override fun resolveTypeName(): TypeName {
+
+ override fun resolveJTypeName(): JTypeName {
// always box these. For primitives, typeName might return the primitive type but if we
// wanted it to be a primitive, we would've resolved it to [KspPrimitiveType].
- return ksType.typeName(env.resolver).tryBox()
+ return ksType.asJTypeName(env.resolver).tryBox()
+ }
+
+ override fun resolveKTypeName(): KTypeName {
+ return ksType.asKTypeName(env.resolver)
}
override fun boxed(): DefaultKspType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index b773e09..aa157d0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -17,222 +17,14 @@
package androidx.room.compiler.processing.ksp
import androidx.room.compiler.processing.XNullability
-import androidx.room.compiler.processing.javac.kotlin.typeNameFromJvmSignature
-import androidx.room.compiler.processing.tryBox
-import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
-import com.google.devtools.ksp.KspExperimental
-import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSDeclaration
-import com.google.devtools.ksp.symbol.KSName
import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.symbol.KSType
-import com.google.devtools.ksp.symbol.KSTypeAlias
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.Modifier
-import com.google.devtools.ksp.symbol.Variance
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
-import com.squareup.javapoet.WildcardTypeName
-import kotlin.coroutines.Continuation
-
-// Catch-all type name when we cannot resolve to anything. This is what KAPT uses as error type
-// and we use the same type in KSP for consistency.
-// https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
-internal val ERROR_TYPE_NAME = ClassName.get("error", "NonExistentClass")
-
-/**
- * To handle self referencing types and avoid infinite recursion, we keep a lookup map for
- * TypeVariables.
- */
-private typealias TypeArgumentTypeLookup = LinkedHashMap<KSName, TypeName>
-
-/**
- * Turns a KSTypeReference into a TypeName in java's type system.
- */
-internal fun KSTypeReference?.typeName(resolver: Resolver): TypeName =
- typeName(
- resolver = resolver,
- typeArgumentTypeLookup = TypeArgumentTypeLookup()
- )
-
-private fun KSTypeReference?.typeName(
- resolver: Resolver,
- typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
- return if (this == null) {
- ERROR_TYPE_NAME
- } else {
- resolve().typeName(resolver, typeArgumentTypeLookup)
- }
-}
-
-/**
- * Turns a KSDeclaration into a TypeName in java's type system.
- */
-internal fun KSDeclaration.typeName(resolver: Resolver): TypeName =
- typeName(
- resolver = resolver,
- typeArgumentTypeLookup = TypeArgumentTypeLookup()
- )
-
-@OptIn(KspExperimental::class)
-private fun KSDeclaration.typeName(
- resolver: Resolver,
- typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
- if (this is KSTypeAlias) {
- return this.type.typeName(resolver, typeArgumentTypeLookup)
- }
- if (this is KSTypeParameter) {
- return this.typeName(resolver, typeArgumentTypeLookup)
- }
- // if there is no qualified name, it is a resolution error so just return shared instance
- // KSP may improve that later and if not, we can improve it in Room
- // TODO: https://issuetracker.google.com/issues/168639183
- val qualified = qualifiedName?.asString() ?: return ERROR_TYPE_NAME
- val jvmSignature = resolver.mapToJvmSignature(this)
- if (jvmSignature != null && jvmSignature.isNotBlank()) {
- return jvmSignature.typeNameFromJvmSignature()
- }
-
- // fallback to custom generation, it is very likely that this is an unresolved type
- // get the package name first, it might throw for invalid types, hence we use
- // safeGetPackageName
- val pkg = getNormalizedPackageName()
- // using qualified name and pkg, figure out the short names.
- val shortNames = if (pkg == "") {
- qualified
- } else {
- qualified.substring(pkg.length + 1)
- }.split('.')
- return ClassName.get(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
-}
-
-/**
- * Turns a KSTypeArgument into a TypeName in java's type system.
- */
-internal fun KSTypeArgument.typeName(
- resolver: Resolver
-): TypeName = typeName(
- resolver = resolver,
- typeArgumentTypeLookup = TypeArgumentTypeLookup()
-)
-
-private fun KSTypeParameter.typeName(
- resolver: Resolver,
- typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
- // see https://github.com/square/javapoet/issues/842
- typeArgumentTypeLookup[name]?.let {
- return it
- }
- val mutableBounds = mutableListOf<TypeName>()
- val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
- typeArgumentTypeLookup[name] = typeName
- val resolvedBounds = bounds.map {
- it.typeName(resolver, typeArgumentTypeLookup).tryBox()
- }.toList()
- if (resolvedBounds.isNotEmpty()) {
- mutableBounds.addAll(resolvedBounds)
- mutableBounds.remove(TypeName.OBJECT)
- }
- typeArgumentTypeLookup.remove(name)
- return typeName
-}
-
-private fun KSTypeArgument.typeName(
- resolver: Resolver,
- typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
- fun resolveTypeName() = type.typeName(resolver, typeArgumentTypeLookup).tryBox()
- return when (variance) {
- Variance.CONTRAVARIANT -> WildcardTypeName.supertypeOf(resolveTypeName())
- Variance.COVARIANT -> WildcardTypeName.subtypeOf(resolveTypeName())
- Variance.STAR -> {
- WildcardTypeName.subtypeOf(TypeName.OBJECT)
- }
- else -> {
- if (hasJvmWildcardAnnotation()) {
- WildcardTypeName.subtypeOf(resolveTypeName())
- } else {
- resolveTypeName()
- }
- }
- }
-}
-
-/**
- * Turns a KSType into a TypeName in java's type system.
- */
-internal fun KSType.typeName(resolver: Resolver): TypeName =
- typeName(
- resolver = resolver,
- typeArgumentTypeLookup = TypeArgumentTypeLookup()
- )
-
-private fun KSType.typeName(
- resolver: Resolver,
- typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
- return if (this.arguments.isNotEmpty() && !isRaw()) {
- val args: Array<TypeName> = this.arguments
- .map { typeArg ->
- typeArg.typeName(
- resolver = resolver,
- typeArgumentTypeLookup = typeArgumentTypeLookup
- )
- }
- .map { it.tryBox() }
- .let { args ->
- if (this.isSuspendFunctionType) args.convertToSuspendSignature()
- else args
- }
- .toTypedArray()
-
- when (
- val typeName = declaration
- .typeName(resolver, typeArgumentTypeLookup).tryBox()
- ) {
- is ArrayTypeName -> ArrayTypeName.of(args.single())
- is ClassName -> ParameterizedTypeName.get(
- typeName,
- *args
- )
- else -> error("Unexpected type name for KSType: $typeName")
- }
- } else {
- this.declaration.typeName(resolver, typeArgumentTypeLookup)
- }
-}
-
-/**
- * Transforms [this] list of arguments to a suspend signature. For a [suspend] functional type, we
- * need to transform it to be a FunctionX with a [Continuation] with the correct return type. A
- * transformed SuspendFunction looks like this:
- *
- * FunctionX<[? super $params], ? super Continuation<? super $ReturnType>, ?>
- */
-private fun List<TypeName>.convertToSuspendSignature(): List<TypeName> {
- val args = this
-
- // The last arg is the return type, so take everything except the last arg
- val actualArgs = args.subList(0, args.size - 1)
- val continuationReturnType = WildcardTypeName.supertypeOf(args.last())
- val continuationType = ParameterizedTypeName.get(
- ClassName.get(Continuation::class.java),
- continuationReturnType
- )
- return actualArgs + listOf(
- WildcardTypeName.supertypeOf(continuationType),
- WildcardTypeName.subtypeOf(TypeName.OBJECT)
- )
-}
/**
* Root package comes as <root> instead of "" so we work around it here.
@@ -265,70 +57,26 @@
else -> throw IllegalArgumentException("Cannot set KSType nullability to platform")
}
-/**
- * The private constructor of [TypeVariableName] which receives a list.
- * We use this in [createModifiableTypeVariableName] to create a [TypeVariableName] whose bounds
- * can be modified afterwards.
- */
-private val typeVarNameConstructor by lazy {
- try {
- TypeVariableName::class.java.getDeclaredConstructor(
- String::class.java,
- List::class.java
- ).also {
- it.trySetAccessible()
- }
- } catch (ex: NoSuchMethodException) {
- throw IllegalStateException(
- """
- Room couldn't find the constructor it is looking for in JavaPoet.
- Please file a bug at $ISSUE_TRACKER_LINK.
- """.trimIndent(),
- ex
- )
- }
-}
-
-/**
- * Creates a TypeVariableName where we can change the bounds after constructor.
- * This is used to workaround a case for self referencing type declarations.
- * see b/187572913 for more details
- */
-private fun createModifiableTypeVariableName(
- name: String,
- bounds: List<TypeName>
-): TypeVariableName = typeVarNameConstructor.newInstance(
- name,
- bounds
-) as TypeVariableName
-
private fun KSAnnotated.hasAnnotation(
- qName: String
-) = annotations.any {
- it.annotationType.resolve().declaration.qualifiedName?.asString() == qName
-}
+ qName: String
+ ) = annotations.any {
+ it.annotationType.resolve().declaration.qualifiedName?.asString() == qName
+ }
-internal fun KSAnnotated.hasJvmWildcardAnnotation() = hasAnnotation(
- JvmWildcard::class.java.canonicalName
-)
+ internal fun KSAnnotated.hasJvmWildcardAnnotation() = hasAnnotation(
+ JvmWildcard::class.java.canonicalName!!
+ )
-internal fun KSAnnotated.hasSuppressJvmWildcardAnnotation() = hasAnnotation(
- JvmSuppressWildcards::class.java.canonicalName
-)
+ internal fun KSAnnotated.hasSuppressJvmWildcardAnnotation() = hasAnnotation(
+ JvmSuppressWildcards::class.java.canonicalName!!
+ )
-internal fun KSNode.hasSuppressWildcardsAnnotationInHierarchy(): Boolean {
- (this as? KSAnnotated)?.let {
- if (hasSuppressJvmWildcardAnnotation()) {
- return true
- }
- }
- val parent = parent ?: return false
- return parent.hasSuppressWildcardsAnnotationInHierarchy()
-}
-
-internal fun KSType.isRaw(): Boolean {
- // yes this is gross but KSP itself seems to be doing it as well
- // https://github.com/google/ksp/blob/main/compiler-plugin/
- // src/main/kotlin/com/google/devtools/ksp/symbol/impl/kotlin/KSTypeImpl.kt#L85
- return toString().startsWith("raw ")
-}
\ No newline at end of file
+ internal fun KSNode.hasSuppressWildcardsAnnotationInHierarchy(): Boolean {
+ (this as? KSAnnotated)?.let {
+ if (hasSuppressJvmWildcardAnnotation()) {
+ return true
+ }
+ }
+ val parent = parent ?: return false
+ return parent.hasSuppressWildcardsAnnotationInHierarchy()
+ }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
new file mode 100644
index 0000000..6150713
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2022 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.room.compiler.processing.ksp
+
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.javac.kotlin.typeNameFromJvmSignature
+import androidx.room.compiler.processing.tryBox
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
+import com.squareup.kotlinpoet.javapoet.JWildcardTypeName
+import kotlin.coroutines.Continuation
+
+// Catch-all type name when we cannot resolve to anything. This is what KAPT uses as error type
+// and we use the same type in KSP for consistency.
+// https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
+internal val ERROR_JTYPE_NAME = JClassName.get("error", "NonExistentClass")
+
+/**
+ * To handle self referencing types and avoid infinite recursion, we keep a lookup map for
+ * TypeVariables.
+ */
+private typealias JTypeArgumentTypeLookup = LinkedHashMap<KSName, JTypeName>
+
+/**
+ * Turns a KSTypeReference into a TypeName in java's type system.
+ */
+internal fun KSTypeReference?.asJTypeName(resolver: Resolver): JTypeName =
+ asJTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+ )
+
+private fun KSTypeReference?.asJTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+ return if (this == null) {
+ ERROR_JTYPE_NAME
+ } else {
+ resolve().asJTypeName(resolver, typeArgumentTypeLookup)
+ }
+}
+
+/**
+ * Turns a KSDeclaration into a TypeName in java's type system.
+ */
+internal fun KSDeclaration.asJTypeName(resolver: Resolver): JTypeName =
+ asJTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+ )
+
+@OptIn(KspExperimental::class)
+private fun KSDeclaration.asJTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+ if (this is KSTypeAlias) {
+ return this.type.asJTypeName(resolver, typeArgumentTypeLookup)
+ }
+ if (this is KSTypeParameter) {
+ return this.asJTypeName(resolver, typeArgumentTypeLookup)
+ }
+ // if there is no qualified name, it is a resolution error so just return shared instance
+ // KSP may improve that later and if not, we can improve it in Room
+ // TODO: https://issuetracker.google.com/issues/168639183
+ val qualified = qualifiedName?.asString() ?: return ERROR_JTYPE_NAME
+ val jvmSignature = resolver.mapToJvmSignature(this)
+ if (jvmSignature != null && jvmSignature.isNotBlank()) {
+ return jvmSignature.typeNameFromJvmSignature()
+ }
+
+ // fallback to custom generation, it is very likely that this is an unresolved type
+ // get the package name first, it might throw for invalid types, hence we use
+ // safeGetPackageName
+ val pkg = getNormalizedPackageName()
+ // using qualified name and pkg, figure out the short names.
+ val shortNames = if (pkg == "") {
+ qualified
+ } else {
+ qualified.substring(pkg.length + 1)
+ }.split('.')
+ return JClassName.get(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
+}
+
+/**
+ * Turns a KSTypeArgument into a TypeName in java's type system.
+ */
+internal fun KSTypeArgument.asJTypeName(
+ resolver: Resolver
+): JTypeName = asJTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+)
+
+private fun KSTypeParameter.asJTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+ // see https://github.com/square/javapoet/issues/842
+ typeArgumentTypeLookup[name]?.let {
+ return it
+ }
+ val mutableBounds = mutableListOf<JTypeName>()
+ val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
+ typeArgumentTypeLookup[name] = typeName
+ val resolvedBounds = bounds.map {
+ it.asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+ }.toList()
+ if (resolvedBounds.isNotEmpty()) {
+ mutableBounds.addAll(resolvedBounds)
+ mutableBounds.remove(JTypeName.OBJECT)
+ }
+ typeArgumentTypeLookup.remove(name)
+ return typeName
+}
+
+private fun KSTypeArgument.asJTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+ fun resolveTypeName() = type.asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+ return when (variance) {
+ Variance.CONTRAVARIANT -> JWildcardTypeName.supertypeOf(resolveTypeName())
+ Variance.COVARIANT -> JWildcardTypeName.subtypeOf(resolveTypeName())
+ Variance.STAR -> {
+ JWildcardTypeName.subtypeOf(JTypeName.OBJECT)
+ }
+ else -> {
+ if (hasJvmWildcardAnnotation()) {
+ JWildcardTypeName.subtypeOf(resolveTypeName())
+ } else {
+ resolveTypeName()
+ }
+ }
+ }
+}
+
+/**
+ * Turns a KSType into a TypeName in java's type system.
+ */
+internal fun KSType.asJTypeName(resolver: Resolver): JTypeName =
+ asJTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+ )
+
+@OptIn(KspExperimental::class)
+private fun KSType.asJTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+ return if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this)) {
+ val args: Array<JTypeName> = this.arguments
+ .map { typeArg ->
+ typeArg.asJTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = typeArgumentTypeLookup
+ )
+ }
+ .map { it.tryBox() }
+ .let { args ->
+ if (this.isSuspendFunctionType) args.convertToSuspendSignature()
+ else args
+ }
+ .toTypedArray()
+
+ when (
+ val typeName = declaration
+ .asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+ ) {
+ is JArrayTypeName -> JArrayTypeName.of(args.single())
+ is JClassName -> JParameterizedTypeName.get(
+ typeName,
+ *args
+ )
+ else -> error("Unexpected type name for KSType: $typeName")
+ }
+ } else {
+ this.declaration.asJTypeName(resolver, typeArgumentTypeLookup)
+ }
+}
+
+/**
+ * Transforms [this] list of arguments to a suspend signature. For a [suspend] functional type, we
+ * need to transform it to be a FunctionX with a [Continuation] with the correct return type. A
+ * transformed SuspendFunction looks like this:
+ *
+ * FunctionX<[? super $params], ? super Continuation<? super $ReturnType>, ?>
+ */
+private fun List<JTypeName>.convertToSuspendSignature(): List<JTypeName> {
+ val args = this
+
+ // The last arg is the return type, so take everything except the last arg
+ val actualArgs = args.subList(0, args.size - 1)
+ val continuationReturnType = JWildcardTypeName.supertypeOf(args.last())
+ val continuationType = JParameterizedTypeName.get(
+ JClassName.get(Continuation::class.java),
+ continuationReturnType
+ )
+ return actualArgs + listOf(
+ JWildcardTypeName.supertypeOf(continuationType),
+ JWildcardTypeName.subtypeOf(JTypeName.OBJECT)
+ )
+}
+
+/**
+ * The private constructor of [JTypeVariableName] which receives a list.
+ * We use this in [createModifiableTypeVariableName] to create a [JTypeVariableName] whose bounds
+ * can be modified afterwards.
+ */
+private val typeVarNameConstructor by lazy {
+ try {
+ JTypeVariableName::class.java.getDeclaredConstructor(
+ String::class.java,
+ List::class.java
+ ).also {
+ it.trySetAccessible()
+ }
+ } catch (ex: NoSuchMethodException) {
+ throw IllegalStateException(
+ """
+ Room couldn't find the constructor it is looking for in JavaPoet.
+ Please file a bug at $ISSUE_TRACKER_LINK.
+ """.trimIndent(),
+ ex
+ )
+ }
+}
+
+/**
+ * Creates a TypeVariableName where we can change the bounds after constructor.
+ * This is used to workaround a case for self referencing type declarations.
+ * see b/187572913 for more details
+ */
+private fun createModifiableTypeVariableName(
+ name: String,
+ bounds: List<JTypeName>
+): JTypeVariableName = typeVarNameConstructor.newInstance(
+ name,
+ bounds
+) as JTypeVariableName
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
new file mode 100644
index 0000000..a862b1b
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2022 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.room.compiler.processing.ksp
+
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.KClassName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeVariableName
+import com.squareup.kotlinpoet.javapoet.KWildcardTypeName
+
+internal val ERROR_KTYPE_NAME = KClassName("error", "NonExistentClass")
+
+private typealias KTypeArgumentTypeLookup = LinkedHashMap<KSName, KTypeName>
+
+internal fun KSTypeReference?.asKTypeName(resolver: Resolver): KTypeName =
+ asKTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+ )
+
+private fun KSTypeReference?.asKTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+ return if (this == null) {
+ ERROR_KTYPE_NAME
+ } else {
+ resolve().asKTypeName(resolver, typeArgumentTypeLookup)
+ }
+}
+
+internal fun KSDeclaration.asKTypeName(resolver: Resolver): KTypeName =
+ asKTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+ )
+
+private fun KSDeclaration.asKTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+ if (this is KSTypeAlias) {
+ return this.type.asKTypeName(resolver, typeArgumentTypeLookup)
+ }
+ if (this is KSTypeParameter) {
+ return this.asKTypeName(resolver, typeArgumentTypeLookup)
+ }
+ val qualified = qualifiedName?.asString() ?: return ERROR_KTYPE_NAME
+ val pkg = getNormalizedPackageName()
+ val shortNames = if (pkg == "") {
+ qualified
+ } else {
+ qualified.substring(pkg.length + 1)
+ }.split('.')
+ return KClassName(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
+}
+
+private fun KSTypeParameter.asKTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+ typeArgumentTypeLookup[name]?.let {
+ return it
+ }
+ val mutableBounds = mutableListOf(ANY.copy(nullable = true))
+ val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
+ typeArgumentTypeLookup[name] = typeName
+ val resolvedBounds = bounds.map {
+ it.asKTypeName(resolver, typeArgumentTypeLookup)
+ }.toList()
+ if (resolvedBounds.isNotEmpty()) {
+ mutableBounds.addAll(resolvedBounds)
+ mutableBounds.remove(ANY.copy(nullable = true))
+ }
+ typeArgumentTypeLookup.remove(name)
+ return typeName
+}
+
+internal fun KSTypeArgument.asKTypeName(
+ resolver: Resolver
+): KTypeName = asKTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+)
+
+private fun KSTypeArgument.asKTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+ fun resolveTypeName() = type.asKTypeName(resolver, typeArgumentTypeLookup)
+ return when (variance) {
+ Variance.CONTRAVARIANT -> KWildcardTypeName.consumerOf(resolveTypeName())
+ Variance.COVARIANT -> KWildcardTypeName.producerOf(resolveTypeName())
+ Variance.STAR -> com.squareup.kotlinpoet.STAR
+ else -> {
+ if (hasJvmWildcardAnnotation()) {
+ KWildcardTypeName.consumerOf(resolveTypeName())
+ } else {
+ resolveTypeName()
+ }
+ }
+ }
+}
+
+internal fun KSType.asKTypeName(resolver: Resolver): KTypeName =
+ asKTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+ )
+
+@OptIn(KspExperimental::class)
+private fun KSType.asKTypeName(
+ resolver: Resolver,
+ typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+ return if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this)) {
+ val args: List<KTypeName> = this.arguments
+ .map { typeArg ->
+ typeArg.asKTypeName(
+ resolver = resolver,
+ typeArgumentTypeLookup = typeArgumentTypeLookup
+ )
+ }
+ val typeName = declaration.asKTypeName(resolver, typeArgumentTypeLookup)
+ check(typeName is KClassName) { "Unexpected type name for KSType: $typeName" }
+ typeName.parameterizedBy(args)
+ } else {
+ this.declaration.asKTypeName(resolver, typeArgumentTypeLookup)
+ }.copy(nullable = isMarkedNullable)
+}
+
+/**
+ * Creates a TypeVariableName where we can change the bounds after constructor.
+ * This is used to workaround a case for self referencing type declarations.
+ */
+private fun createModifiableTypeVariableName(
+ name: String,
+ bounds: List<KTypeName>
+): KTypeVariableName = KTypeVariableNameFactory.newInstance(name, bounds)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java
new file mode 100644
index 0000000..79489d3
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 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.room.compiler.processing.ksp;
+
+import com.squareup.kotlinpoet.TypeName;
+import com.squareup.kotlinpoet.TypeVariableName;
+
+import java.util.List;
+
+final class KTypeVariableNameFactory {
+ private KTypeVariableNameFactory() {
+ }
+
+ /**
+ * Calls the internal companion object of KTypeVariableName which receives a list.
+ * We use this in {@link KSTypeJavaPoetExtKt#createModifiableTypeVariableName(String, List)}
+ * to create a {@link com.squareup.kotlinpoet.TypeVariableName} whose bounds can be modified
+ * afterwards.
+ */
+ @SuppressWarnings("KotlinInternalInJava")
+ static TypeVariableName newInstance(String name, List<TypeName> bounds) {
+ return TypeVariableName.Companion.of$kotlinpoet(name, bounds, null);
+ }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
index 2da4cad..7dc6993 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
@@ -16,13 +16,17 @@
package androidx.room.compiler.processing.ksp
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XArrayType
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.Variance
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
internal sealed class KspArrayType(
env: KspProcessingEnv,
@@ -35,8 +39,12 @@
abstract override val componentType: KspType
- override fun resolveTypeName(): TypeName {
- return ArrayTypeName.of(componentType.typeName)
+ override fun resolveJTypeName(): JTypeName {
+ return this.asTypeName().java
+ }
+
+ override fun resolveKTypeName(): KTypeName {
+ return this.asTypeName().kotlin
}
override fun boxed() = this
@@ -54,6 +62,17 @@
) : KspArrayType(
env, ksType, jvmTypeResolver
) {
+ private val xTypeName: XTypeName by lazy {
+ val componentTypeName = componentType.asTypeName()
+ XTypeName(
+ java = JArrayTypeName.of(componentTypeName.java.box()),
+ kotlin = ARRAY.parameterizedBy(componentTypeName.kotlin),
+ nullability = nullability,
+ )
+ }
+
+ override fun asTypeName() = xTypeName
+
override val componentType: KspType by lazy {
val arg = ksType.arguments.single()
// https://kotlinlang.org/docs/reference/basic-types.html#primitive-type-arrays
@@ -92,6 +111,17 @@
) : KspArrayType(
env, ksType, jvmTypeResolver
) {
+ private val xTypeName: XTypeName by lazy {
+ val componentTypeName = componentType.asTypeName()
+ XTypeName(
+ java = JArrayTypeName.of(componentTypeName.java.unbox()),
+ kotlin = ksType.asKTypeName(env.resolver),
+ nullability = nullability,
+ )
+ }
+
+ override fun asTypeName() = xTypeName
+
override fun copyWithNullability(nullability: XNullability): PrimitiveArray {
return PrimitiveArray(
env = env,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
index bd3bab4..ec72704 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
@@ -29,7 +29,7 @@
override val typeVariableNames: List<TypeVariableName> by lazy {
origin.declaration.typeParameters.map {
val typeParameterBounds = it.bounds.map {
- it.typeName(env.resolver)
+ it.asJTypeName(env.resolver)
}.toList().toTypedArray()
TypeVariableName.get(
it.name.asString(),
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
index ac8c34e..771dba1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
@@ -19,7 +19,8 @@
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.tryUnbox
import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
/**
* This tries to mimic primitive types in Kotlin.
@@ -33,8 +34,12 @@
ksType: KSType,
jvmTypeResolver: KspJvmTypeResolver?
) : KspType(env, ksType, jvmTypeResolver) {
- override fun resolveTypeName(): TypeName {
- return ksType.typeName(env.resolver).tryUnbox()
+ override fun resolveJTypeName(): JTypeName {
+ return ksType.asJTypeName(env.resolver).tryUnbox()
+ }
+
+ override fun resolveKTypeName(): KTypeName {
+ return ksType.asKTypeName(env.resolver)
}
override fun boxed(): KspType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 8cacecb..876c02a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing.ksp
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
@@ -28,6 +29,8 @@
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.Nullability
import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
import kotlin.reflect.KClass
/**
@@ -51,11 +54,21 @@
}
final override val typeName: TypeName by lazy {
- jvmWildcardType?.typeName ?: resolveTypeName()
+ xTypeName.java
}
+ private val xTypeName: XTypeName by lazy {
+ XTypeName(
+ jvmWildcardType?.typeName ?: resolveJTypeName(),
+ resolveKTypeName(),
+ nullability
+ )
+ }
+
+ override fun asTypeName() = xTypeName
+
/**
- * A Kotlin type might have a sligtly different type in JVM due to wildcards.
+ * A Kotlin type might have a slightly different type in JVM due to wildcards.
* This fields holds onto that value which will be used when creating JVM types.
*/
private val jvmWildcardType by lazy {
@@ -65,7 +78,9 @@
val jvmWildcardTypeOrSelf
get() = jvmWildcardType ?: this
- protected abstract fun resolveTypeName(): TypeName
+ protected abstract fun resolveJTypeName(): JTypeName
+
+ protected abstract fun resolveKTypeName(): KTypeName
override val nullability by lazy {
when (ksType.nullability) {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index a247fb6..f7471ae 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -21,7 +21,8 @@
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
/**
* The typeName for type arguments requires the type parameter, hence we have a special type
@@ -48,8 +49,12 @@
)
}
- override fun resolveTypeName(): TypeName {
- return typeArg.typeName(env.resolver)
+ override fun resolveJTypeName(): JTypeName {
+ return typeArg.asJTypeName(env.resolver)
+ }
+
+ override fun resolveKTypeName(): KTypeName {
+ return typeArg.asKTypeName(env.resolver)
}
override fun boxed(): KspTypeArgumentType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index bf0c668..75517f7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing.ksp
+import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.processing.XAnnotated
import androidx.room.compiler.processing.XConstructorElement
import androidx.room.compiler.processing.XEnumEntry
@@ -24,6 +25,7 @@
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMemberContainer
import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.XTypeParameterElement
@@ -44,6 +46,8 @@
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.KClassName
internal sealed class KspTypeElement(
env: KspProcessingEnv,
@@ -130,14 +134,22 @@
}
override val className: ClassName by lazy {
- declaration.typeName(env.resolver).tryBox().also { typeName ->
- check(typeName is ClassName) {
+ xClassName.java
+ }
+
+ private val xClassName: XClassName by lazy {
+ val java = declaration.asJTypeName(env.resolver).tryBox().also { typeName ->
+ check(typeName is JClassName) {
"Internal error. The type name for $declaration should be a class name but " +
"received ${typeName::class}"
}
- } as ClassName
+ } as JClassName
+ val kotlin = declaration.asKTypeName(env.resolver) as KClassName
+ XClassName(java, kotlin, XNullability.NONNULL)
}
+ override fun asClassName() = xClassName
+
private val allMethods = MemoizedSequence {
collectAllMethods(this)
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
index 497280f6..87d50ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
@@ -18,7 +18,8 @@
import androidx.room.compiler.processing.XNullability
import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
/**
* Representation of `void` in KSP.
@@ -33,14 +34,18 @@
val boxed: Boolean,
jvmTypeResolver: KspJvmTypeResolver?
) : KspType(env, ksType, jvmTypeResolver) {
- override fun resolveTypeName(): TypeName {
+ override fun resolveJTypeName(): JTypeName {
return if (boxed || nullability == XNullability.NULLABLE) {
- TypeName.VOID.box()
+ JTypeName.VOID.box()
} else {
- TypeName.VOID
+ JTypeName.VOID
}
}
+ override fun resolveKTypeName(): KTypeName {
+ return com.squareup.kotlinpoet.UNIT
+ }
+
override fun boxed(): KspType {
return if (boxed) {
this
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
index 1bbe125..96a2bc9 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
@@ -16,18 +16,21 @@
package androidx.room.compiler.processing
+import androidx.room.compiler.codegen.JArrayTypeName
import androidx.room.compiler.processing.ksp.KspProcessingEnv
import androidx.room.compiler.processing.ksp.createTypeReference
import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.asJTypeName
+import androidx.room.compiler.processing.util.asKTypeName
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.kspResolver
import androidx.room.compiler.processing.util.runKspTest
import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.compiler.processing.util.typeName
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
import org.junit.Test
class XArrayTypeTest {
@@ -50,12 +53,20 @@
.getField("param")
.type
assertThat(type.isArray()).isTrue()
- assertThat(type.typeName).isEqualTo(
- ArrayTypeName.of(String::class.java)
+ assertThat(type.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(String::class.java)
)
+ if (invocation.isKsp) {
+ assertThat(type.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(String::class.asKTypeName())
+ )
+ }
check(type.isArray())
type.componentType.let { component ->
- assertThat(component.typeName).isEqualTo(String::class.typeName())
+ assertThat(component.asTypeName().java).isEqualTo(String::class.asJTypeName())
+ if (invocation.isKsp) {
+ assertThat(component.asTypeName().kotlin).isEqualTo(String::class.asKTypeName())
+ }
assertThat(component.nullability).isEqualTo(XNullability.UNKNOWN)
}
}
@@ -64,16 +75,30 @@
@Test
fun synthetic() {
runProcessorTest {
- val objArray = it.processingEnv.getArrayType(
- TypeName.OBJECT
+ fun checkObjectArray(objArray: XArrayType) {
+ check(objArray.isArray())
+ assertThat(objArray.componentType.asTypeName().java)
+ .isEqualTo(JTypeName.OBJECT)
+ assertThat(objArray.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(JTypeName.OBJECT)
+ )
+ if (it.isKsp) {
+ assertThat(objArray.componentType.asTypeName().kotlin)
+ .isEqualTo(com.squareup.kotlinpoet.ANY)
+ assertThat(objArray.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.ANY)
+ )
+ }
+ }
+ checkObjectArray(
+ it.processingEnv.getArrayType(it.processingEnv.requireType("java.lang.Object"))
)
- check(objArray.isArray())
- assertThat(objArray.componentType.typeName).isEqualTo(
- TypeName.OBJECT
- )
- assertThat(objArray.typeName).isEqualTo(
- ArrayTypeName.of(TypeName.OBJECT)
- )
+ if (it.isKsp) {
+ // javac can't resolve Any
+ checkObjectArray(
+ it.processingEnv.getArrayType(it.processingEnv.requireType("kotlin.Any"))
+ )
+ }
}
}
@@ -93,25 +118,51 @@
sources = listOf(source)
) { invocation ->
val element = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
- val nonNull = element.getField("nonNull").type
- val nullable = element.getField("nullable").type
- listOf(nonNull, nullable).forEach {
- assertThat(it.isArray()).isTrue()
- assertThat(it.typeName).isEqualTo(
- ArrayTypeName.of(String::class.java)
+ element.getField("nonNull").type.let { nonNull ->
+ check(nonNull.isArray())
+ assertThat(nonNull.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(String::class.java)
)
+ if (invocation.isKsp) {
+ assertThat(nonNull.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(String::class.asKTypeName())
+ )
+ }
+ nonNull.componentType.let { component ->
+ assertThat(component.asTypeName().java).isEqualTo(
+ String::class.asJTypeName()
+ )
+ if (invocation.isKsp) {
+ assertThat(component.asTypeName().kotlin).isEqualTo(
+ String::class.asKTypeName()
+ )
+ }
+ assertThat(component.nullability).isEqualTo(XNullability.NONNULL)
+ }
}
- check(nonNull.isArray())
- nonNull.componentType.let { component ->
- assertThat(component.typeName).isEqualTo(
- String::class.typeName()
+ element.getField("nullable").type.let { nullable ->
+ check(nullable.isArray())
+ assertThat(nullable.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(String::class.java)
)
- assertThat(component.nullability).isEqualTo(XNullability.NONNULL)
- }
- check(nullable.isArray())
- nullable.componentType.let { component ->
- assertThat(component.typeName).isEqualTo(String::class.typeName())
- assertThat(component.nullability).isEqualTo(XNullability.NULLABLE)
+ if (invocation.isKsp) {
+ assertThat(nullable.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(
+ String::class.asKTypeName().copy(nullable = true)
+ )
+ )
+ }
+ nullable.componentType.let { component ->
+ assertThat(component.asTypeName().java).isEqualTo(
+ String::class.asJTypeName()
+ )
+ if (invocation.isKsp) {
+ assertThat(component.asTypeName().kotlin).isEqualTo(
+ String::class.asKTypeName().copy(nullable = true)
+ )
+ }
+ assertThat(component.nullability).isEqualTo(XNullability.NULLABLE)
+ }
}
}
}
@@ -141,29 +192,119 @@
}
""".trimIndent()
)
- runProcessorTest(listOf(src)) {
- val subject = it.processingEnv.requireTypeElement("Subject")
+ runProcessorTest(listOf(src)) { invocation ->
+ class Container(
+ val method: String,
+ val jTypeName: JTypeName,
+ val kTypeName: KTypeName
+ ) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as Container
+ if (method != other.method) return false
+ if (jTypeName != other.jTypeName) return false
+ if (invocation.isKsp) {
+ if (kTypeName != other.kTypeName) return false
+ }
+ return true
+ }
+ override fun hashCode(): Int {
+ var result = method.hashCode()
+ result = 31 * result + jTypeName.hashCode()
+ if (invocation.isKsp) {
+ result = 31 * result + kTypeName.hashCode()
+ }
+ return result
+ }
+ }
+
+ val subject = invocation.processingEnv.requireTypeElement("Subject")
val types = subject.getAllFieldsIncludingPrivateSupers().map {
assertWithMessage(it.name).that(it.type.isArray()).isTrue()
- it.name to it.type.typeName
+ Container(it.name, it.type.asTypeName().java, it.type.asTypeName().kotlin)
}.toList()
assertThat(types).containsExactly(
- "primitiveBooleanArray" to ArrayTypeName.of(TypeName.BOOLEAN),
- "primitiveByteArray" to ArrayTypeName.of(TypeName.BYTE),
- "primitiveShortArray" to ArrayTypeName.of(TypeName.SHORT),
- "primitiveIntArray" to ArrayTypeName.of(TypeName.INT),
- "primitiveLongArray" to ArrayTypeName.of(TypeName.LONG),
- "primitiveCharArray" to ArrayTypeName.of(TypeName.CHAR),
- "primitiveFloatArray" to ArrayTypeName.of(TypeName.FLOAT),
- "primitiveDoubleArray" to ArrayTypeName.of(TypeName.DOUBLE),
- "boxedBooleanArray" to ArrayTypeName.of(TypeName.BOOLEAN.box()),
- "boxedByteArray" to ArrayTypeName.of(TypeName.BYTE.box()),
- "boxedShortArray" to ArrayTypeName.of(TypeName.SHORT.box()),
- "boxedIntArray" to ArrayTypeName.of(TypeName.INT.box()),
- "boxedLongArray" to ArrayTypeName.of(TypeName.LONG.box()),
- "boxedCharArray" to ArrayTypeName.of(TypeName.CHAR.box()),
- "boxedFloatArray" to ArrayTypeName.of(TypeName.FLOAT.box()),
- "boxedDoubleArray" to ArrayTypeName.of(TypeName.DOUBLE.box())
+ Container(
+ "primitiveBooleanArray",
+ JArrayTypeName.of(JTypeName.BOOLEAN),
+ com.squareup.kotlinpoet.BOOLEAN_ARRAY
+ ),
+ Container(
+ "primitiveByteArray",
+ JArrayTypeName.of(JTypeName.BYTE),
+ com.squareup.kotlinpoet.BYTE_ARRAY
+ ),
+ Container(
+ "primitiveShortArray",
+ JArrayTypeName.of(JTypeName.SHORT),
+ com.squareup.kotlinpoet.SHORT_ARRAY
+ ),
+ Container(
+ "primitiveIntArray",
+ JArrayTypeName.of(JTypeName.INT),
+ com.squareup.kotlinpoet.INT_ARRAY
+ ),
+ Container(
+ "primitiveLongArray",
+ JArrayTypeName.of(JTypeName.LONG),
+ com.squareup.kotlinpoet.LONG_ARRAY
+ ),
+ Container(
+ "primitiveCharArray",
+ JArrayTypeName.of(JTypeName.CHAR),
+ com.squareup.kotlinpoet.CHAR_ARRAY
+ ),
+ Container(
+ "primitiveFloatArray",
+ JArrayTypeName.of(JTypeName.FLOAT),
+ com.squareup.kotlinpoet.FLOAT_ARRAY
+ ),
+ Container(
+ "primitiveDoubleArray",
+ JArrayTypeName.of(JTypeName.DOUBLE),
+ com.squareup.kotlinpoet.DOUBLE_ARRAY
+ ),
+ Container(
+ "boxedBooleanArray",
+ JArrayTypeName.of(JTypeName.BOOLEAN.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.BOOLEAN)
+ ),
+ Container(
+ "boxedByteArray",
+ JArrayTypeName.of(JTypeName.BYTE.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.BYTE)
+ ),
+ Container(
+ "boxedShortArray",
+ JArrayTypeName.of(JTypeName.SHORT.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.SHORT)
+ ),
+ Container(
+ "boxedIntArray",
+ JArrayTypeName.of(JTypeName.INT.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.INT)
+ ),
+ Container(
+ "boxedLongArray",
+ JArrayTypeName.of(JTypeName.LONG.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.LONG)
+ ),
+ Container(
+ "boxedCharArray",
+ JArrayTypeName.of(JTypeName.CHAR.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.CHAR)
+ ),
+ Container(
+ "boxedFloatArray",
+ JArrayTypeName.of(JTypeName.FLOAT.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.FLOAT)
+ ),
+ Container(
+ "boxedDoubleArray",
+ JArrayTypeName.of(JTypeName.DOUBLE.box()),
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.DOUBLE)
+ )
)
}
}
@@ -177,8 +318,11 @@
invocation.processingEnv.getArrayType(intType).let {
assertThat(it.isArray()).isTrue()
assertThat(it.componentType).isEqualTo(intType)
- assertThat(it.typeName).isEqualTo(
- ArrayTypeName.of(TypeName.INT)
+ assertThat(it.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(JTypeName.INT)
+ )
+ assertThat(it.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.INT_ARRAY
)
}
val nullableInt = (invocation.processingEnv as KspProcessingEnv).wrap(
@@ -188,8 +332,13 @@
invocation.processingEnv.getArrayType(nullableInt).let {
assertThat(it.isArray()).isTrue()
assertThat(it.componentType).isEqualTo(nullableInt)
- assertThat(it.typeName).isEqualTo(
- ArrayTypeName.of(TypeName.INT.box())
+ assertThat(it.asTypeName().java).isEqualTo(
+ JArrayTypeName.of(JTypeName.INT.box())
+ )
+ assertThat(it.asTypeName().kotlin).isEqualTo(
+ com.squareup.kotlinpoet.ARRAY.parameterizedBy(
+ com.squareup.kotlinpoet.INT.copy(nullable = true)
+ )
)
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index c1134d4..248cb25 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -29,6 +30,9 @@
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeVariableName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KClassName
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -67,29 +71,52 @@
assertThat(it.packageName).isEqualTo("")
assertThat(it.name).isEqualTo("TopLevel")
assertThat(it.qualifiedName).isEqualTo("TopLevel")
- assertThat(it.className).isEqualTo(ClassName.get("", "TopLevel"))
+ assertThat(it.asClassName().java)
+ .isEqualTo(JClassName.get("", "TopLevel"))
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin)
+ .isEqualTo(KClassName("", "TopLevel"))
+ } else {
+ assertThat(it.asClassName().kotlin)
+ .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+ }
}
invocation.processingEnv.requireTypeElement("foo.bar.InFooBar").let {
assertThat(it.packageName).isEqualTo("foo.bar")
assertThat(it.name).isEqualTo("InFooBar")
assertThat(it.qualifiedName).isEqualTo("foo.bar.InFooBar")
- assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "InFooBar"))
+ assertThat(it.asClassName().java)
+ .isEqualTo(ClassName.get("foo.bar", "InFooBar"))
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin)
+ .isEqualTo(KClassName("foo.bar", "InFooBar"))
+ }
}
invocation.processingEnv.requireTypeElement("foo.bar.Outer").let {
assertThat(it.packageName).isEqualTo("foo.bar")
assertThat(it.name).isEqualTo("Outer")
assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer")
- assertThat(it.className).isEqualTo(
+ assertThat(it.asClassName().java).isEqualTo(
ClassName.get("foo.bar", "Outer")
)
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin).isEqualTo(
+ KClassName("foo.bar", "Outer")
+ )
+ }
}
invocation.processingEnv.requireTypeElement("foo.bar.Outer.Nested").let {
assertThat(it.packageName).isEqualTo("foo.bar")
assertThat(it.name).isEqualTo("Nested")
assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer.Nested")
- assertThat(it.className).isEqualTo(
+ assertThat(it.asClassName().java).isEqualTo(
ClassName.get("foo.bar", "Outer", "Nested")
)
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin).isEqualTo(
+ KClassName("foo.bar", "Outer", "Nested")
+ )
+ }
}
if (invocation.isKsp) {
// these are KSP specific tests, typenames are tested elsewhere
@@ -98,11 +125,19 @@
assertThat(it.packageName).isEqualTo("kotlin")
assertThat(it.name).isEqualTo("Int")
assertThat(it.qualifiedName).isEqualTo("kotlin.Int")
+ assertThat(it.asClassName().java).isEqualTo(JTypeName.INT.box())
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin).isEqualTo(com.squareup.kotlinpoet.INT)
+ }
}
invocation.processingEnv.requireTypeElement("kotlin.Int").let {
assertThat(it.packageName).isEqualTo("kotlin")
assertThat(it.name).isEqualTo("Int")
assertThat(it.qualifiedName).isEqualTo("kotlin.Int")
+ assertThat(it.asClassName().java).isEqualTo(JTypeName.INT.box())
+ if (invocation.isKsp) {
+ assertThat(it.asClassName().kotlin).isEqualTo(com.squareup.kotlinpoet.INT)
+ }
}
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index c1ebb8e..f83ebeb 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -16,7 +16,7 @@
package androidx.room.compiler.processing
-import androidx.room.compiler.processing.ksp.ERROR_TYPE_NAME
+import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.className
@@ -121,7 +121,7 @@
val errorTypeName = if (it.isKsp) {
// in ksp, we lose the name when resolving the type.
// b/175246617
- ERROR_TYPE_NAME
+ ERROR_JTYPE_NAME
} else {
ClassName.get("", "NotExistingType")
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
index ad531fd..61eee03 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
@@ -60,16 +60,18 @@
listOf("main", "lib").map {
it to invocation.kspResolver.requireClass("$it.Baz")
}.forEach { (pkg, subject) ->
- assertThat(subject.propertyType("intField").typeName(invocation.kspResolver))
+ assertThat(subject.propertyType("intField").asJTypeName(invocation.kspResolver))
.isEqualTo(TypeName.INT)
- assertThat(subject.propertyType("listOfInts").typeName(invocation.kspResolver))
+ assertThat(subject.propertyType("listOfInts").asJTypeName(invocation.kspResolver))
.isEqualTo(
ParameterizedTypeName.get(
List::class.className(),
TypeName.INT.box()
)
)
- assertThat(subject.propertyType("mutableMapOfAny").typeName(invocation.kspResolver))
+ assertThat(
+ subject.propertyType("mutableMapOfAny").asJTypeName(invocation.kspResolver)
+ )
.isEqualTo(
ParameterizedTypeName.get(
Map::class.className(),
@@ -77,7 +79,7 @@
TypeName.OBJECT,
)
)
- val typeName = subject.propertyType("nested").typeName(invocation.kspResolver)
+ val typeName = subject.propertyType("nested").asJTypeName(invocation.kspResolver)
check(typeName is ClassName)
assertThat(typeName.packageName()).isEqualTo(pkg)
assertThat(typeName.simpleNames()).containsExactly("Baz", "Nested")
@@ -114,11 +116,11 @@
}.forEach { subject ->
assertWithMessage(subject.qualifiedName!!.asString())
.that(
- subject.propertyType("intField").typeName(invocation.kspResolver)
+ subject.propertyType("intField").asJTypeName(invocation.kspResolver)
).isEqualTo(TypeName.INT)
assertWithMessage(subject.qualifiedName!!.asString())
.that(
- subject.propertyType("listOfInts").typeName(invocation.kspResolver)
+ subject.propertyType("listOfInts").asJTypeName(invocation.kspResolver)
).isEqualTo(
ParameterizedTypeName.get(
List::class.className(),
@@ -126,14 +128,15 @@
)
)
val propertyType = subject.propertyType("incompleteGeneric")
- val typeName = propertyType.typeName(invocation.kspResolver)
+ val typeName = propertyType.asJTypeName(invocation.kspResolver)
assertWithMessage(subject.qualifiedName!!.asString())
.that(
typeName
).isEqualTo(
ClassName.get(List::class.java)
)
- val nestedTypeName = subject.propertyType("nested").typeName(invocation.kspResolver)
+ val nestedTypeName =
+ subject.propertyType("nested").asJTypeName(invocation.kspResolver)
assertWithMessage(subject.qualifiedName!!.asString())
.that(nestedTypeName)
.isEqualTo(
@@ -159,23 +162,23 @@
runKspTest(sources = listOf(subjectSrc)) { invocation ->
val subject = invocation.kspResolver.requireClass("Foo")
assertThat(
- subject.propertyType("errorField").typeName(invocation.kspResolver)
- ).isEqualTo(ERROR_TYPE_NAME)
+ subject.propertyType("errorField").asJTypeName(invocation.kspResolver)
+ ).isEqualTo(ERROR_JTYPE_NAME)
assertThat(
- subject.propertyType("listOfError").typeName(invocation.kspResolver)
+ subject.propertyType("listOfError").asJTypeName(invocation.kspResolver)
).isEqualTo(
ParameterizedTypeName.get(
List::class.className(),
- ERROR_TYPE_NAME
+ ERROR_JTYPE_NAME
)
)
assertThat(
- subject.propertyType("mutableMapOfDontExist").typeName(invocation.kspResolver)
+ subject.propertyType("mutableMapOfDontExist").asJTypeName(invocation.kspResolver)
).isEqualTo(
ParameterizedTypeName.get(
Map::class.className(),
String::class.className(),
- ERROR_TYPE_NAME
+ ERROR_JTYPE_NAME
)
)
invocation.assertCompilationResult {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index 9eb4b0d..f9c6d5f 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -99,8 +99,8 @@
subject.getField("errorType").type.let { type ->
assertThat(type.isError()).isTrue()
assertThat(type.typeArguments).isEmpty()
- assertThat(type.typeName).isEqualTo(ERROR_TYPE_NAME)
- assertThat(type.typeElement!!.className).isEqualTo(ERROR_TYPE_NAME)
+ assertThat(type.typeName).isEqualTo(ERROR_JTYPE_NAME)
+ assertThat(type.typeElement!!.className).isEqualTo(ERROR_JTYPE_NAME)
}
subject.getField("listOfErrorType").type.let { type ->
@@ -108,7 +108,7 @@
assertThat(type.typeArguments).hasSize(1)
type.typeArguments.single().let { typeArg ->
assertThat(typeArg.isError()).isTrue()
- assertThat(typeArg.typeName).isEqualTo(ERROR_TYPE_NAME)
+ assertThat(typeArg.typeName).isEqualTo(ERROR_JTYPE_NAME)
}
}
invocation.assertCompilationResult {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt
deleted file mode 100644
index 8f7be96..0000000
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2020 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.room.compiler.processing.util
-
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import kotlin.reflect.KClass
-
-val UNIT_CLASS_NAME = ClassName.get("kotlin", "Unit")
-val CONTINUATION_CLASS_NAME = ClassName.get("kotlin.coroutines", "Continuation")
-
-fun KClass<*>.typeName() = TypeName.get(this.java)
-fun KClass<*>.className() = ClassName.get(this.java)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
new file mode 100644
index 0000000..df6eeec
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2020 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.room.compiler.processing.util
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
+import com.squareup.kotlinpoet.javapoet.KParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeVariableName
+import kotlin.reflect.KClass
+
+val UNIT_CLASS_NAME = ClassName.get("kotlin", "Unit")
+val CONTINUATION_CLASS_NAME = ClassName.get("kotlin.coroutines", "Continuation")
+
+// TODO(b/247242378): Migrate usages to asJTypeName() and asJClassName()
+// @Deprecated(
+// message = "Use asJTypeName() to be clear it's a JavaPoet converter",
+// replaceWith = ReplaceWith("asJTypeName()")
+// )
+fun KClass<*>.typeName() = TypeName.get(this.java)
+
+// TODO(b/247242378): Migrate usages to asJTypeName() and asJClassName()
+// @Deprecated(
+// message = "Use asJClassName() to be clear it's a JavaPoet converter",
+// replaceWith = ReplaceWith("asJClassName()")
+// )
+fun KClass<*>.className() = ClassName.get(this.java)
+
+fun KClass<*>.asJTypeName() = TypeName.get(this.java)
+fun KClass<*>.asJClassName() = ClassName.get(this.java)
+
+fun KClass<*>.asKTypeName() = this.asTypeName()
+fun KClass<*>.asKClassName() = this.asClassName()
+
+/**
+ * Dumps the typename with its bounds in a given depth, making tests more readable.
+ */
+fun JTypeName.dumpToString(depth: Int): String {
+ return dump(this, depth).toString()
+}
+
+/**
+ * Dumps the typename with its bounds in a given depth, making tests more readable.
+ */
+fun KTypeName.dumpToString(depth: Int): String {
+ return dump(this, depth).toString()
+}
+
+private fun dump(typeName: Any, depth: Int): TypeNameNode? {
+ if (depth < 0) return null
+ return when (typeName) {
+ is JParameterizedTypeName -> TypeNameNode(
+ text = typeName.toString(),
+ typeArgs = typeName.typeArguments.mapNotNull { dump(it, depth - 1) }
+ )
+ is KParameterizedTypeName -> TypeNameNode(
+ text = typeName.toString(),
+ typeArgs = typeName.typeArguments.mapNotNull { dump(it, depth - 1) }
+ )
+ is JTypeVariableName -> TypeNameNode(
+ text = typeName.toString(),
+ bounds = typeName.bounds.mapNotNull { dump(it, depth - 1) }
+ )
+ is KTypeVariableName -> TypeNameNode(
+ text = typeName.toString(),
+ bounds = typeName.bounds.mapNotNull { dump(it, depth - 1) }
+ )
+ else -> TypeNameNode(text = typeName.toString())
+ }
+}
+
+private data class TypeNameNode(
+ val text: String,
+ val bounds: List<TypeNameNode> = emptyList(),
+ val typeArgs: List<TypeNameNode> = emptyList()
+) {
+ override fun toString(): String {
+ return buildString {
+ appendLine(text)
+ bounds.forEach {
+ appendLine(it.toString().prependIndent("> "))
+ }
+ typeArgs.forEach {
+ appendLine(it.toString().prependIndent("| "))
+ }
+ }.trim()
+ }
+}
diff --git a/room/room-compiler/lint-baseline.xml b/room/room-compiler/lint-baseline.xml
new file mode 100644
index 0000000..0b7f2c4
--- /dev/null
+++ b/room/room-compiler/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @VisibleForTesting"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt"/>
+ </issue>
+
+</issues>
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
index 4cbf4637..3e60540 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
@@ -16,11 +16,11 @@
package androidx.room.vo
+import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.processing.XTypeElement
import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.FieldBundle
import androidx.room.util.SchemaDiffResult
-import com.squareup.javapoet.ClassName
/**
* Stores the changes detected in a database schema between the old and new versions.
@@ -34,10 +34,10 @@
) {
val specClassName = specElement?.className
- fun getImplTypeName(databaseClassName: ClassName): ClassName {
- return ClassName.get(
- databaseClassName.packageName(),
- "${databaseClassName.simpleNames().joinToString("_")}_AutoMigration_${from}_${to}_Impl"
+ fun getImplTypeName(databaseClassName: XClassName): XClassName {
+ return XClassName.get(
+ databaseClassName.packageName,
+ "${databaseClassName.simpleNames.joinToString("_")}_AutoMigration_${from}_${to}_Impl"
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index c6cdbe8..55cbec8 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -24,7 +24,7 @@
import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.codegen.addOriginatingElement
-import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.codegen.toXClassName
import androidx.room.compiler.processing.XTypeElement
import androidx.room.ext.RoomTypeNames
import androidx.room.ext.SupportDbTypeNames
@@ -32,7 +32,6 @@
import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.FtsEntityBundle
import androidx.room.vo.AutoMigration
-import com.squareup.javapoet.TypeName
import com.squareup.kotlinpoet.javapoet.toKClassName
/**
@@ -42,7 +41,7 @@
private val dbElement: XTypeElement,
val autoMigration: AutoMigration,
private val codeLanguage: CodeLanguage
-) : TypeWriter(autoMigration.getImplTypeName(dbElement.className)) {
+) : TypeWriter(autoMigration.getImplTypeName(dbElement.asClassName())) {
private val addedColumns = autoMigration.schemaDiff.addedColumns
private val addedTables = autoMigration.schemaDiff.addedTables
private val renamedTables = autoMigration.schemaDiff.renamedTables
@@ -52,17 +51,16 @@
override fun createTypeSpecBuilder(): XTypeSpec.Builder {
val builder = XTypeSpec.classBuilder(
codeLanguage,
- autoMigration.getImplTypeName(dbElement.className)
+ autoMigration.getImplTypeName(dbElement.asClassName())
)
builder.apply {
addOriginatingElement(dbElement)
- superclass(RoomTypeNames.MIGRATION)
+ superclass(RoomTypeNames.MIGRATION.toXClassName())
if (autoMigration.specClassName != null) {
builder.addProperty(
- typeName = RoomTypeNames.AUTO_MIGRATION_SPEC,
+ typeName = RoomTypeNames.AUTO_MIGRATION_SPEC.toXClassName(),
name = "callback",
- nullability = XNullability.NONNULL,
visibility = VisibilityModifier.PRIVATE,
initExpr = if (!autoMigration.isSpecProvided) {
XCodeBlock.builder(codeLanguage).apply(
@@ -97,9 +95,8 @@
)
if (autoMigration.isSpecProvided) {
addParameter(
- typeName = RoomTypeNames.AUTO_MIGRATION_SPEC,
+ typeName = RoomTypeNames.AUTO_MIGRATION_SPEC.toXClassName(),
name = "callback",
- nullability = XNullability.NONNULL
)
addStatement("this.callback = callback")
}
@@ -114,11 +111,9 @@
isOverridden = true,
).apply {
addParameter(
- typeName = SupportDbTypeNames.DB,
+ typeName = SupportDbTypeNames.DB.toXClassName(),
name = "database",
- nullability = XNullability.NONNULL
)
- returns(TypeName.VOID, XNullability.NONNULL)
addMigrationStatements(this)
if (autoMigration.specClassName != null) {
addStatement("callback.onPostMigrate(database)")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index 6e5db80..7a3e9c5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -17,6 +17,8 @@
package androidx.room.writer
import androidx.annotation.NonNull
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.toXClassName
import androidx.room.compiler.processing.MethodSpecHelper
import androidx.room.compiler.processing.addOriginatingElement
import androidx.room.ext.AndroidTypeNames
@@ -368,15 +370,16 @@
returns(ParameterizedTypeName.get(CommonTypeNames.LIST, RoomTypeNames.MIGRATION))
val autoMigrationsList = database.autoMigrations.map { autoMigrationResult ->
- val implTypeName = autoMigrationResult.getImplTypeName(database.typeName)
+ val implTypeName =
+ autoMigrationResult.getImplTypeName(database.typeName.toXClassName())
if (autoMigrationResult.isSpecProvided) {
CodeBlock.of(
"new $T(autoMigrationSpecsMap.get($T.class))",
- implTypeName,
+ implTypeName.toJavaPoet(),
autoMigrationResult.specClassName
)
} else {
- CodeBlock.of("new $T()", implTypeName)
+ CodeBlock.of("new $T()", implTypeName.toJavaPoet())
}
}
addStatement(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
index 349b407..4cfe79c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
@@ -17,19 +17,19 @@
package androidx.room.writer
import androidx.room.RoomProcessor
+import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.apply
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.writeTo
import androidx.room.ext.S
-import com.squareup.kotlinpoet.javapoet.JClassName
import com.squareup.kotlinpoet.javapoet.toKClassName
import kotlin.reflect.KClass
/**
* Base class for all writers that can produce a class.
*/
-abstract class TypeWriter(private val className: JClassName) {
+abstract class TypeWriter(private val className: XClassName) {
private val metadata = mutableMapOf<KClass<*>, Any>()
abstract fun createTypeSpecBuilder(): XTypeSpec.Builder
diff --git a/wear/watchface/watchface/lint-baseline.xml b/wear/watchface/watchface/lint-baseline.xml
new file mode 100644
index 0000000..3e45aa6
--- /dev/null
+++ b/wear/watchface/watchface/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:RestrictTo`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/wear/watchface/ComplicationSlot.kt"/>
+ </issue>
+
+</issues>
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 7fb57ff..51d8a7d 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1340,6 +1340,10 @@
} catch (e: Exception) {
InteractiveInstanceManager
.takePendingWallpaperInteractiveWatchFaceInstance()?.let {
+ Log.e(
+ TAG,
+ "takePendingWallpaperInteractiveWatchFaceInstance failed"
+ )
it.callback.onInteractiveWatchFaceCrashed(
CrashInfoParcel(e)
)
@@ -1362,6 +1366,7 @@
pendingWallpaperInstance.callback.onInteractiveWatchFaceCreated(instance)
instance
} catch (e: Exception) {
+ Log.e(TAG, "createInteractiveInstance failed")
pendingWallpaperInstance.callback.onInteractiveWatchFaceCrashed(
CrashInfoParcel(e)
)
diff --git a/window/window/lint-baseline.xml b/window/window/lint-baseline.xml
new file mode 100644
index 0000000..a84ae6d
--- /dev/null
+++ b/window/window/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+ <issue
+ id="SupportAnnotationUsage"
+ message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+ errorLine1=" @VisibleForTesting"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/window/layout/SidecarCompat.kt"/>
+ </issue>
+
+</issues>