Merge "Provide ability for tests to send hardware key events to Owner" into androidx-master-dev
diff --git a/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt b/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
index 920aea7..4aaa75c 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
@@ -65,7 +65,7 @@
// scaling_min_freq, or -1 if can't access
currentMinFreq = readFileTextOrNull("$path/cpufreq/scaling_min_freq")?.toInt()
?: -1,
- maxFreqKhz = readFileTextOrNull("$path/cpuinfo_max_freq")?.toLong() ?: -1L
+ maxFreqKhz = readFileTextOrNull("$path/cpufreq/cpuinfo_max_freq")?.toLong() ?: -1L
)
} ?: emptyList()
@@ -75,10 +75,8 @@
?.maxFreqKhz?.times(1000) ?: -1
locked = isCpuLocked(coreDirs)
- if (!locked) {
- coreDirs.forEachIndexed { index, coreDir ->
- Log.d(TAG, "cpu$index $coreDir")
- }
+ coreDirs.forEachIndexed { index, coreDir ->
+ Log.d(TAG, "cpu$index $coreDir")
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 3628eb0..f56db6d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -62,7 +62,7 @@
prebuilts(LibraryGroups.CORE, "core-ktx", "1.5.0-alpha01")
prebuilts(LibraryGroups.CORE, "core-role", "1.1.0-alpha02")
prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
- prebuilts(LibraryGroups.CUSTOMVIEW, "1.1.0-rc01")
+ prebuilts(LibraryGroups.CUSTOMVIEW, "1.1.0")
prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
prebuilts(LibraryGroups.DRAWERLAYOUT, "1.1.0-rc01")
prebuilts(LibraryGroups.DYNAMICANIMATION, "dynamicanimation-ktx", "1.0.0-alpha03")
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index a229644..a1bc83d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -109,7 +109,6 @@
// The list of checks that are API lint warnings and are yet to be enabled
"ExecutorRegistration",
- "NotCloseable",
"SamShouldBeLast",
"MissingJvmstatic",
"CallbackMethodName",
@@ -134,6 +133,7 @@
"ListenerLast",
"StreamFiles",
"AbstractInner",
+ "NotCloseable",
"ArrayReturn",
"MethodNameTense",
"UseIcu",
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index fc84262..2a57e15 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -32,5 +32,7 @@
export JAVA_TOOLS_JAR="$JAVA_HOME/lib/tools.jar"
export LINT_PRINT_STACKTRACE=true
-$gw -p frameworks/support --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
-DIST_SUBDIR="/ui" $gw -p frameworks/support/ui --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
+LOG_SIMPLIFIER="$SCRIPT_DIR/../development/build_log_simplifier.sh"
+
+"$LOG_SIMPLIFIER" $gw -p frameworks/support --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
+"$LOG_SIMPLIFIER" DIST_SUBDIR="/ui" $gw -p frameworks/support/ui --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 4ec9bde..c400e90 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -30,7 +30,7 @@
# display the contents of the out/ directory, to allow us to be sure that it was empty before this build started
echoAndDo "ls -la out"
-# run gradle
-# "androidx.summarizeStderr" outputs an error summary message. See gradlew for details
-echoAndDo OUT_DIR=out/ui DIST_DIR=$DIST_DIR/ui ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/ui/gradlew -p frameworks/support/ui --stacktrace -Pandroidx.summarizeStderr "$@"
-echoAndDo OUT_DIR=out DIST_DIR=$DIST_DIR ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/gradlew -p frameworks/support --stacktrace -Pandroidx.summarizeStderr "$@"
+LOG_SIMPLIFIER="$SCRIPT_DIR/../../development/build_log_simplifier.sh"
+
+"$LOG_SIMPLIFIER" OUT_DIR=out/ui DIST_DIR=$DIST_DIR/ui ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/ui/gradlew -p frameworks/support/ui --stacktrace -Pandroidx.summarizeStderr "$@"
+"$LOG_SIMPLIFIER" OUT_DIR=out DIST_DIR=$DIST_DIR ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/gradlew -p frameworks/support --stacktrace -Pandroidx.summarizeStderr "$@"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index 125d103..e5f9a13 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -17,19 +17,30 @@
package androidx.camera.camera2.pipe
import android.content.Context
+import android.os.HandlerThread
import androidx.camera.camera2.pipe.impl.CameraGraphModule
import androidx.camera.camera2.pipe.impl.CameraPipeComponent
import androidx.camera.camera2.pipe.impl.CameraPipeModule
import androidx.camera.camera2.pipe.impl.DaggerCameraPipeComponent
/**
- * Interface to configure and access the camera device wrappers.
+ * [CameraPipe] is the top level scope for all interactions with a Camera2 camera.
+ *
+ * Under most circumstances an application should only ever have a single instance of a [CameraPipe]
+ * object as each instance will cache expensive calls and operations with the Android Camera
+ * framework. In addition to the caching behaviors it will optimize the access and configuration of
+ * [android.hardware.camera2.CameraDevice] and [android.hardware.camera2.CameraCaptureSession] via
+ * the [CameraGraph] interface.
*/
-class CameraPipe(private val config: CameraPipe.Config) {
+class CameraPipe(config: Config) {
private val component: CameraPipeComponent = DaggerCameraPipeComponent.builder()
.cameraPipeModule(CameraPipeModule(config))
.build()
+ /**
+ * This creates a new [CameraGraph] that can be used to interact with a single Camera on the
+ * device. Multiple [CameraGraph]s can be created, but only one should be active at a time.
+ */
fun create(config: CameraGraph.Config): CameraGraph {
return component.cameraGraphComponentBuilder()
.cameraGraphModule(CameraGraphModule(config))
@@ -37,7 +48,19 @@
.cameraGraph()
}
+ /**
+ * This provides access to information about the available cameras on the device.
+ */
+ fun cameras(): Cameras {
+ return component.cameras()
+ }
+
+ /**
+ * This is the application level configuration for [CameraPipe]. Nullable values are optional
+ * and reasonable defaults will be provided if the values are not specified.
+ */
data class Config(
- val appContext: Context
+ val appContext: Context,
+ val cameraThread: HandlerThread? = null
)
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt
new file mode 100644
index 0000000..f82b0cf
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.camera.camera2.pipe
+
+/**
+ * Methods for querying, iterating, and selecting the Cameras that are available on the device.
+ */
+interface Cameras {
+ /**
+ * Iterate and return a list of CameraId's on the device that are capable of being opened. Some
+ * camera devices may be hidden or un-openable if they are included as part of a logical camera
+ * group.
+ */
+ fun findAll(): List<CameraId>
+
+ /**
+ * Load CameraMetadata for a specific CameraId. Loading CameraMetadata can take a
+ * non-zero amount of time to execute. If CameraMetadata is not already cached this function
+ * will suspend until CameraMetadata can be loaded.
+ */
+ suspend fun getMetadata(camera: CameraId): CameraMetadata
+
+ /**
+ * Load CameraMetadata for a specific CameraId and block the calling thread until the result is
+ * available.
+ */
+ fun awaitMetadata(camera: CameraId): CameraMetadata
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index e4c92e5..94efcef 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -19,6 +19,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
import androidx.camera.camera2.pipe.impl.Debug
import java.util.concurrent.ConcurrentHashMap
@@ -32,9 +33,8 @@
*/
interface Metadata {
operator fun <T> get(key: Key<T>): T?
-
- fun <T> getOrDefault(key: Key<T>, default: T): T
fun <T> getChecked(key: Key<T>): T
+ fun <T> getOrDefault(key: Key<T>, default: T): T
/**
* Metadata keys provide values or controls that are provided or computed by CameraPipe.
@@ -63,14 +63,26 @@
/**
* CameraMetadata is a wrapper around [CameraCharacteristics].
+ *
+ * In some cases the properties on this interface will provide faster or more backwards compatible
+ * access to features that are only available on newer versions of the OS.
*/
interface CameraMetadata : Metadata, UnsafeWrapper<CameraCharacteristics> {
operator fun <T> get(key: CameraCharacteristics.Key<T>): T?
-
- fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T
fun <T> getChecked(key: CameraCharacteristics.Key<T>): T
+ fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T
val camera: CameraId
+ val isRedacted: Boolean
+
+ val keys: Set<CameraCharacteristics.Key<*>>
+ val requestKeys: Set<CaptureRequest.Key<*>>
+ val resultKeys: Set<CaptureResult.Key<*>>
+ val sessionKeys: Set<CaptureRequest.Key<*>>
+ val physicalCameraIds: Set<CameraId>
+ val physicalRequestKeys: Set<CaptureRequest.Key<*>>
+
+ val streamMap: StreamConfigurationMap
}
/**
@@ -78,9 +90,8 @@
*/
interface RequestMetadata : Metadata, UnsafeWrapper<CaptureRequest> {
operator fun <T> get(key: CaptureRequest.Key<T>): T?
-
- fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
fun <T> getChecked(key: CaptureRequest.Key<T>): T
+ fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
}
/**
@@ -88,9 +99,8 @@
*/
interface ResultMetadata : Metadata, UnsafeWrapper<CaptureResult> {
operator fun <T> get(key: CaptureResult.Key<T>): T?
-
- fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T
fun <T> getChecked(key: CaptureResult.Key<T>): T
+ fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T
val camera: CameraId
val request: RequestMetadata
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
new file mode 100644
index 0000000..40cd98b
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.camera.camera2.pipe.impl
+
+import android.Manifest.permission
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraManager
+import android.os.Build
+import android.util.ArrayMap
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides caching and querying of CameraMetadata.
+ *
+ * This class is designed to be thread safe and provides suspend functions for querying and
+ * accessing CameraMetadata.
+ */
+@Singleton
+class CameraMetadataCache @Inject constructor(
+ private val context: Context
+) {
+ @GuardedBy("cache")
+ private val cache = ArrayMap<String, CameraMetadata>()
+
+ @Volatile
+ private var hasCameraPermission = false
+
+ suspend fun get(cameraId: CameraId): CameraMetadata {
+ synchronized(cache) {
+ val existing = cache[cameraId.value]
+ if (existing != null) {
+ return existing
+ }
+ }
+
+ // Suspend and query CameraMetadata on a background thread.
+ return withContext(Dispatchers.IO) {
+ awaitMetadata(cameraId)
+ }
+ }
+
+ fun awaitMetadata(cameraId: CameraId): CameraMetadata {
+ return Debug.trace("awaitMetadata") {
+ synchronized(cache) {
+ val existing = cache[cameraId.value]
+ if (existing != null) {
+ return@trace existing
+ } else if (!isMetadataRedacted()) {
+ val result = createCameraMetadata(cameraId, false)
+ cache[cameraId.value] = result
+ return@trace result
+ }
+ }
+ return@trace createCameraMetadata(cameraId, true)
+ }
+ }
+
+ private fun createCameraMetadata(cameraId: CameraId, redacted: Boolean): CameraMetadataImpl {
+ val cameraManager =
+ context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val characteristics =
+ cameraManager.getCameraCharacteristics(cameraId.value)
+ return CameraMetadataImpl(cameraId, redacted, characteristics, emptyMap())
+ }
+
+ private fun isMetadataRedacted(): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ // Some CameraCharacteristic properties are redacted on Q or higher if the application
+ // does not currently hold the CAMERA permission.
+ return !checkCameraPermission()
+ }
+ return false
+ }
+
+ @RequiresApi(23)
+ private fun checkCameraPermission(): Boolean {
+ // Granted camera permission is cached here to reduce the number of binder transactions
+ // executed. This is considered okay because when a user revokes a permission at runtime,
+ // Android's PermissionManagerService kills the app via the onPermissionRevoked callback,
+ // allowing the code to avoid re-querying after checkSelfPermission returns true.
+ if (!hasCameraPermission &&
+ context.checkSelfPermission(permission.CAMERA) == PackageManager.PERMISSION_GRANTED
+ ) {
+ hasCameraPermission = true
+ }
+ return hasCameraPermission
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
new file mode 100644
index 0000000..3e761b9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.Metadata
+
+/**
+ * This implementation provides access to CameraCharacteristics and lazy caching of properties
+ * that are either expensive to create and access, or that only exist on newer versions of the
+ * OS.
+ */
+class CameraMetadataImpl constructor(
+ override val camera: CameraId,
+ override val isRedacted: Boolean,
+ private val characteristics: CameraCharacteristics,
+ private val metadata: Map<Metadata.Key<*>, Any?>
+) : CameraMetadata {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> get(key: Metadata.Key<T>): T? = metadata[key] as T?
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> getChecked(key: Metadata.Key<T>): T = (metadata[key] as T)!!
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> getOrDefault(key: Metadata.Key<T>, default: T): T =
+ metadata[key] as T? ?: default
+
+ override fun <T> get(key: CameraCharacteristics.Key<T>): T? = characteristics[key]
+ override fun <T> getChecked(key: CameraCharacteristics.Key<T>): T = characteristics[key]!!
+ override fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T =
+ characteristics[key] ?: default
+
+ override fun unwrap(): CameraCharacteristics? = characteristics
+
+ override val keys: Set<CameraCharacteristics.Key<*>> get() = _keys.value
+ override val requestKeys: Set<CaptureRequest.Key<*>> get() = _requestKeys.value
+ override val resultKeys: Set<CaptureResult.Key<*>> get() = _resultKeys.value
+ override val sessionKeys: Set<CaptureRequest.Key<*>> get() = _sessionKeys.value
+ override val physicalCameraIds: Set<CameraId> get() = _physicalCameraIds.value
+ override val physicalRequestKeys: Set<CaptureRequest.Key<*>>
+ get() = _physicalRequestKeys.value
+
+ override val streamMap: StreamConfigurationMap get() = _streamMap.value
+
+ private val _keys: Lazy<Set<CameraCharacteristics.Key<*>>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.keys.orEmpty().toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CameraCharacteristics.Key<*>>()
+ }
+ }
+
+ private val _requestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.availableCaptureRequestKeys.orEmpty().toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CaptureRequest.Key<*>>()
+ }
+ }
+
+ private val _resultKeys: Lazy<Set<CaptureResult.Key<*>>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.availableCaptureResultKeys.orEmpty().toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CaptureResult.Key<*>>()
+ }
+ }
+
+ private val _physicalCameraIds: Lazy<Set<CameraId>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ emptySet()
+ } else {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.physicalCameraIds.orEmpty().map { CameraId(it) }.toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CameraId>()
+ }
+ }
+ }
+
+ private val _physicalRequestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ emptySet()
+ } else {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.availablePhysicalCameraRequestKeys.orEmpty().toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CaptureRequest.Key<*>>()
+ }
+ }
+ }
+
+ private val _sessionKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ emptySet()
+ } else {
+ try {
+ @Suppress("UselessCallOnNotNull")
+ characteristics.availableSessionKeys.orEmpty().toSet()
+ } catch (ignored: AssertionError) {
+ emptySet<CaptureRequest.Key<*>>()
+ }
+ }
+ }
+
+ private val _streamMap: Lazy<StreamConfigurationMap> =
+ lazy(LazyThreadSafetyMode.PUBLICATION) {
+ getChecked(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
index 5470d42..3cd222f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
@@ -17,23 +17,44 @@
package androidx.camera.camera2.pipe.impl
import android.content.Context
+import android.hardware.camera2.CameraManager
import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.Cameras
+import dagger.Binds
import dagger.Component
import dagger.Module
import dagger.Provides
+import dagger.Reusable
import javax.inject.Singleton
@Singleton
@Component(modules = [CameraPipeModule::class])
interface CameraPipeComponent {
fun cameraGraphComponentBuilder(): CameraGraphComponent.Builder
+ fun cameras(): Cameras
}
-@Module(subcomponents = [CameraGraphComponent::class])
+@Module(
+ includes = [CameraPipeBindings::class],
+ subcomponents = [CameraGraphComponent::class]
+)
class CameraPipeModule(private val config: CameraPipe.Config) {
@Provides
fun provideCameraPipeConfig(): CameraPipe.Config = config
+}
- @Provides
- fun provideCameraPipeContext(): Context = config.appContext
+@Module
+abstract class CameraPipeBindings {
+ @Binds
+ abstract fun bindCameras(impl: CamerasImpl): Cameras
+
+ companion object {
+ @Provides
+ fun provideContext(config: CameraPipe.Config): Context = config.appContext
+
+ @Reusable
+ @Provides
+ fun provideCameraManager(context: Context): CameraManager =
+ context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ }
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt
new file mode 100644
index 0000000..75c9d10
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CameraManager
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.Cameras
+import androidx.camera.camera2.pipe.impl.Debug.trace
+import javax.inject.Inject
+import javax.inject.Provider
+import javax.inject.Singleton
+
+/**
+ * Provides utilities for querying cameras and accessing metadata about those cameras.
+ */
+@Singleton
+class CamerasImpl @Inject constructor(
+ private val cameraManager: Provider<CameraManager>,
+ private val metadata: CameraMetadataCache
+) : Cameras {
+
+ private val cameras = lazy(LazyThreadSafetyMode.PUBLICATION) {
+ // NOTE: Publication safety mode may cause this method to be invoked more than once if there
+ // a race between multiple threads. Only one return value will ultimately be cached.
+
+ trace("cameraIdList") {
+ val cameraManager = cameraManager.get()
+
+ // TODO(b/159052778): Find a way to make this poll for the camera list and to suspend
+ // if the value is not yet available. It will be important to detect and differentiate
+ // between devices that should have cameras, but don't, vs devices that do not
+ // physically have cameras. It may also be worthwhile to re-query if looking for
+ // external cameras.
+
+ // WARNING: This method can, at times, return an empty list of cameras on devices that
+ // will normally return a valid list of cameras (b/159052778)
+ val cameraIdList = cameraManager.cameraIdList
+ cameraIdList.map { CameraId(it) }
+ }
+ }
+
+ override fun findAll(): List<CameraId> = cameras.value
+
+ override suspend fun getMetadata(camera: CameraId): CameraMetadata {
+ return metadata.get(camera)
+ }
+
+ override fun awaitMetadata(camera: CameraId): CameraMetadata {
+ return metadata.awaitMetadata(camera)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
index 069ecf1..f331631 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
@@ -18,11 +18,34 @@
package androidx.camera.camera2.pipe.impl
+import android.os.Trace
+
/**
* Internal debug utilities, constants, and checks.
*/
object Debug {
const val ENABLE_LOGGING = true
+ const val ENABLE_TRACING = true
+
+ /**
+ * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [label])
+ * and [Trace.endSection].
+ *
+ * @param label A name of the code section to appear in the trace.
+ * @param block A block of code which is being traced.
+ */
+ inline fun <T> trace(label: String, crossinline block: () -> T): T {
+ try {
+ if (ENABLE_TRACING) {
+ Trace.beginSection(label)
+ }
+ return block()
+ } finally {
+ if (ENABLE_TRACING) {
+ Trace.endSection()
+ }
+ }
+ }
/**
* Asserts that the provided value *is* null.
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
index 2d9013a..c7292b4 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
@@ -49,4 +49,15 @@
)
assertThat(cameraGraph).isNotNull()
}
+
+ @Test
+ fun iterateCameraIds() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ val cameraPipe = CameraPipe(CameraPipe.Config(context))
+ val cameras = cameraPipe.cameras()
+ val cameraList = cameras.findAll()
+
+ assertThat(cameraList).isNotNull()
+ assertThat(cameraList.size).isEqualTo(0) // Robolectric does not report cameras.
+ }
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
index dce2657..35a2da1 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.os.Build
import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
import androidx.test.core.app.ApplicationProvider
-import androidx.camera.camera2.pipe.CameraId
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt
new file mode 100644
index 0000000..745e8c9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.camera.camera2.pipe.impl
+
+import android.Manifest
+import android.app.Application
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+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.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowApplication
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
+
+@SmallTest
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class CameraMetadataCacheTest {
+ companion object {
+ const val CAMERA0_ID = "0"
+ const val CAMERA0_SUPPORTED_HARDWARE_LEVEL =
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+ const val CAMERA0_SENSOR_ORIENTATION = 90
+ const val CAMERA0_LENS_FACING = CameraCharacteristics.LENS_FACING_BACK
+ const val CAMERA0_FLASH_INFO_AVAILABLE = true
+
+ const val CAMERA1_ID = "1"
+ const val CAMERA1_SUPPORTED_HARDWARE_LEVEL =
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
+ const val CAMERA1_SENSOR_ORIENTATION = 0
+ const val CAMERA1_LENS_FACING_INT = CameraCharacteristics.LENS_FACING_FRONT
+ const val CAMERA1_FLASH_INFO_AVAILABLE = false
+ }
+
+ @Test
+ fun metadataIsCachedAndShimmed() {
+ configureShadowCameras()
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val cache = CameraMetadataCache(context)
+
+ val metadata0 = cache.awaitMetadata(CameraId("0"))
+ val metadata1 = cache.awaitMetadata(CameraId("1"))
+
+ // Check to make sure that metadata is not null, and that various properties do not crash
+ // on older OS versions when accessed.
+ assertThat(metadata0).isNotNull()
+ assertThat(metadata0.camera).isEqualTo(CameraId("0"))
+ assertThat(metadata0.isRedacted).isFalse()
+ assertThat(metadata0.keys).isNotNull()
+ assertThat(metadata0.requestKeys).isNotNull()
+ assertThat(metadata0.resultKeys).isNotNull()
+ assertThat(metadata0.sessionKeys).isNotNull()
+ assertThat(metadata0.physicalCameraIds).isNotNull()
+ assertThat(metadata0.physicalRequestKeys).isNotNull()
+ assertThat(metadata0[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]).isEqualTo(
+ CAMERA0_SUPPORTED_HARDWARE_LEVEL
+ )
+
+ assertThat(metadata1).isNotNull()
+ assertThat(metadata1.camera).isEqualTo(CameraId("1"))
+ assertThat(metadata1.isRedacted).isFalse()
+ assertThat(metadata1.keys).isNotNull()
+ assertThat(metadata1.requestKeys).isNotNull()
+ assertThat(metadata1.resultKeys).isNotNull()
+ assertThat(metadata1.sessionKeys).isNotNull()
+ assertThat(metadata1.physicalCameraIds).isNotNull()
+ assertThat(metadata1.physicalRequestKeys).isNotNull()
+ assertThat(metadata1[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]).isEqualTo(
+ CAMERA1_SUPPORTED_HARDWARE_LEVEL
+ )
+ }
+
+ private fun configureShadowCameras() {
+ val app: Application =
+ ApplicationProvider.getApplicationContext()
+ val shadowApp: ShadowApplication = Shadows.shadowOf(app)
+ shadowApp.grantPermissions(Manifest.permission.CAMERA)
+
+ val shadowCameraManager = Shadow.extract<Any>(
+ app.getSystemService(Context.CAMERA_SERVICE)
+ ) as ShadowCameraManager
+
+ val characteristics0 = ShadowCameraCharacteristics.newCameraCharacteristics()
+ val shadowCharacteristics0 = Shadow.extract<ShadowCameraCharacteristics>(characteristics0)
+ shadowCharacteristics0.set(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,
+ CAMERA0_SUPPORTED_HARDWARE_LEVEL
+ )
+ // Add a lens facing to the camera
+ shadowCharacteristics0.set(
+ CameraCharacteristics.LENS_FACING,
+ CAMERA0_LENS_FACING
+ )
+ // Mock the sensor orientation
+ shadowCharacteristics0.set(
+ CameraCharacteristics.SENSOR_ORIENTATION,
+ CAMERA0_SENSOR_ORIENTATION
+ )
+ // Mock the flash unit availability
+ shadowCharacteristics0.set(
+ CameraCharacteristics.FLASH_INFO_AVAILABLE,
+ CAMERA0_FLASH_INFO_AVAILABLE
+ )
+ // Add the camera to the camera service
+ shadowCameraManager.addCamera(CAMERA0_ID, characteristics0)
+
+ // **** Camera 1 characteristics ****//
+ val characteristics1 =
+ ShadowCameraCharacteristics.newCameraCharacteristics()
+ val shadowCharacteristics1 =
+ Shadow.extract<ShadowCameraCharacteristics>(characteristics1)
+ shadowCharacteristics1.set(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,
+ CAMERA1_SUPPORTED_HARDWARE_LEVEL
+ )
+ // Add a lens facing to the camera
+ shadowCharacteristics1.set(
+ CameraCharacteristics.LENS_FACING,
+ CAMERA1_LENS_FACING_INT
+ )
+ // Mock the sensor orientation
+ shadowCharacteristics1.set(
+ CameraCharacteristics.SENSOR_ORIENTATION,
+ CAMERA1_SENSOR_ORIENTATION
+ )
+ // Mock the flash unit availability
+ shadowCharacteristics1.set(
+ CameraCharacteristics.FLASH_INFO_AVAILABLE,
+ CAMERA1_FLASH_INFO_AVAILABLE
+ )
+ // Add the camera to the camera service
+ shadowCameraManager.addCamera(CAMERA1_ID, characteristics1)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
index 54ec2ab..f88d2c9 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
@@ -21,6 +21,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.Metadata
@@ -65,6 +66,16 @@
override fun <T> getChecked(key: CameraCharacteristics.Key<T>): T = (values[key] as T)!!
override val camera = CameraId("Fake")
+ override val isRedacted = false
+ override val keys: Set<CameraCharacteristics.Key<*>> = emptySet()
+ override val requestKeys: Set<CaptureRequest.Key<*>> = emptySet()
+ override val resultKeys: Set<CaptureResult.Key<*>> = emptySet()
+ override val sessionKeys: Set<CaptureRequest.Key<*>> = emptySet()
+ override val physicalCameraIds: Set<CameraId> = emptySet()
+ override val physicalRequestKeys: Set<CaptureRequest.Key<*>> = emptySet()
+ override val streamMap: StreamConfigurationMap
+ get() = throw UnsupportedOperationException("StreamConfigurationMap is not available.")
+
override fun unwrap(): CameraCharacteristics? {
throw UnsupportedOperationException(
"FakeCameraMetadata does not wrap CameraCharacteristics")
diff --git a/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt b/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
index 2b746ba..8bec130 100644
--- a/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
+++ b/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
@@ -93,6 +93,9 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
+ }
+
public interface ExtendableBuilder<T> {
method public T build();
}
@@ -311,6 +314,35 @@
public abstract class UseCase {
}
+ @androidx.camera.core.ExperimentalUseCaseGroup public final class UseCaseGroup {
+ method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+ method public androidx.camera.core.ViewPort getViewPort();
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static class UseCaseGroup.Builder {
+ ctor public UseCaseGroup.Builder();
+ method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+ method public androidx.camera.core.UseCaseGroup build();
+ method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public final class ViewPort {
+ method public android.util.Rational getAspectRatio();
+ method public int getRotation();
+ method public int getScaleType();
+ field public static final int FILL_CENTER = 1; // 0x1
+ field public static final int FILL_END = 2; // 0x2
+ field public static final int FILL_START = 0; // 0x0
+ field public static final int FIT_CENTER = 4; // 0x4
+ field public static final int FIT_END = 5; // 0x5
+ field public static final int FIT_START = 3; // 0x3
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static class ViewPort.Builder {
+ ctor public ViewPort.Builder(android.util.Rational, int);
+ method public androidx.camera.core.ViewPort build();
+ }
+
public interface ZoomState {
method public float getLinearZoom();
method public float getMaxZoomRatio();
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 2b746ba..8bec130 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -93,6 +93,9 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
+ }
+
public interface ExtendableBuilder<T> {
method public T build();
}
@@ -311,6 +314,35 @@
public abstract class UseCase {
}
+ @androidx.camera.core.ExperimentalUseCaseGroup public final class UseCaseGroup {
+ method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+ method public androidx.camera.core.ViewPort getViewPort();
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static class UseCaseGroup.Builder {
+ ctor public UseCaseGroup.Builder();
+ method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+ method public androidx.camera.core.UseCaseGroup build();
+ method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public final class ViewPort {
+ method public android.util.Rational getAspectRatio();
+ method public int getRotation();
+ method public int getScaleType();
+ field public static final int FILL_CENTER = 1; // 0x1
+ field public static final int FILL_END = 2; // 0x2
+ field public static final int FILL_START = 0; // 0x0
+ field public static final int FIT_CENTER = 4; // 0x4
+ field public static final int FIT_END = 5; // 0x5
+ field public static final int FIT_START = 3; // 0x3
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static class ViewPort.Builder {
+ ctor public ViewPort.Builder(android.util.Rational, int);
+ method public androidx.camera.core.ViewPort build();
+ }
+
public interface ZoomState {
method public float getLinearZoom();
method public float getMaxZoomRatio();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 124ce73..f73f4eb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -215,6 +215,7 @@
@RestrictTo(Scope.LIBRARY_GROUP)
@SuppressWarnings({"lambdaLast", "unused"})
@NonNull
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
public static Camera bindToLifecycle(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@@ -280,9 +281,10 @@
* camera to be used for the given use cases.
* @hide
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
- @UseExperimental(markerClass = ExperimentalCameraFilter.class)
@SuppressWarnings({"lambdaLast", "unused"})
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroup
+ @UseExperimental(markerClass = ExperimentalCameraFilter.class)
@NonNull
public static Camera bindToLifecycle(
@NonNull LifecycleOwner lifecycleOwner,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
new file mode 100644
index 0000000..216a281
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
@@ -0,0 +1,38 @@
+/*
+ * 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.camera.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+
+/**
+ * Denotes that the annotated classes and methods uses the experimental feature which provides
+ * a grouping mechanism for {@link UseCase}s.
+ *
+ * <p> The {@link UseCaseGroup} is a class that groups {@link UseCase}s together. All the
+ * {@link UseCase}s in the same group share certain properties. The {@link ViewPort} is a
+ * collection of shared {@link UseCase} properties for synchronizing the visible rectangle across
+ * all the use cases.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalUseCaseGroup {
+}
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 da97999d..fb9d455 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
@@ -17,7 +17,6 @@
package androidx.camera.core;
import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;
import androidx.lifecycle.Lifecycle;
@@ -31,17 +30,15 @@
* {@link UseCase}s to the same {@link Lifecycle}. {@link UseCase}s inside of a
* {@link UseCaseGroup} usually share some common properties like the FOV defined by
* {@link ViewPort}.
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalUseCaseGroup
public final class UseCaseGroup {
private ViewPort mViewPort;
- private UseCase[] mUseCases;
+ private List<UseCase> mUseCases;
- UseCaseGroup(@NonNull ViewPort viewPort, @NonNull UseCase[] useCases) {
+ UseCaseGroup(@NonNull ViewPort viewPort, @NonNull List<UseCase> useCases) {
mViewPort = viewPort;
mUseCases = useCases;
}
@@ -58,16 +55,14 @@
* Gets the {@link UseCase}s.
*/
@NonNull
- public UseCase[] getUseCases() {
+ public List<UseCase> getUseCases() {
return mUseCases;
}
/**
* A builder for generating {@link UseCaseGroup}.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroup
public static class Builder {
private ViewPort mViewPort;
@@ -80,11 +75,8 @@
/**
* Sets {@link ViewPort} shared by the {@link UseCase}s.
- *
- * @hide
*/
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Builder setViewPort(@NonNull ViewPort viewPort) {
mViewPort = viewPort;
return this;
@@ -92,11 +84,8 @@
/**
* Adds {@link UseCase} to the collection.
- *
- * @hide
*/
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Builder addUseCase(@NonNull UseCase useCase) {
mUseCases.add(useCase);
return this;
@@ -104,14 +93,11 @@
/**
* Builds a {@link UseCaseGroup} from the current state.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
public UseCaseGroup build() {
Preconditions.checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty.");
- return new UseCaseGroup(mViewPort, mUseCases.toArray(new UseCase[0]));
+ return new UseCaseGroup(mViewPort, mUseCases);
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
index 639b079d..095456a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
@@ -17,6 +17,8 @@
package androidx.camera.core;
import android.util.Rational;
+import android.view.Display;
+import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -29,10 +31,8 @@
/**
* The FOV of one or many {@link UseCase}s.
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalUseCaseGroup
public final class ViewPort {
/**
@@ -59,69 +59,65 @@
}
/**
- * Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * container, and align it to the top left corner of the view.
- * This may cause the preview to be cropped if the camera preview aspect ratio does not
- * match that of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it fills the entire {@link ViewPort}, and align it to the start of the
+ * {@link ViewPort}, which is the top left corner in a left-to-right (LTR) layout, or the top
+ * right corner in a right-to-left (RTL) layout.
+ * <p>
+ * This may cause the output to be cropped if the output aspect ratio does not match that of
+ * the {@link ViewPort}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FILL_START = 0;
/**
- * Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * container, and center it inside the view.
- * This may cause the preview to be cropped if the camera preview aspect ratio does not
- * match that of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it fills the entire {@link ViewPort} and center it.
+ * <p>
+ * This may cause the output to be cropped if the output aspect ratio does not match that of
+ * the {@link ViewPort}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FILL_CENTER = 1;
/**
- * Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * container, and align it to the bottom right corner of the view.
- * This may cause the preview to be cropped if the camera preview aspect ratio does not
- * match that of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it fills the entire {@link ViewPort}, and align it to the end of the
+ * {@link ViewPort}, which is the bottom right corner in a left-to-right (LTR) layout, or the
+ * bottom left corner in a right-to-left (RTL) layout.
+ * <p>
+ * This may cause the output to be cropped if the output aspect ratio does not match that of
+ * the {@link ViewPort}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FILL_END = 2;
/**
- * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
- * within the container, and align it to the top left corner of the view.
- * Both dimensions of the preview will be equal or less than the corresponding dimensions
- * of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it is entirely contained within the {@link ViewPort}, and align it to the start
+ * of the {@link ViewPort}, which is the top left corner in a left-to-right (LTR) layout, or
+ * the top right corner in a right-to-left (RTL) layout.
+ * <p>
+ * Both dimensions of the {@link ViewPort} crop rect will be equal or less than the
+ * corresponding dimensions of the output.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FIT_START = 3;
/**
- * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
- * within the container, and center it inside the view.
- * Both dimensions of the preview will be equal or less than the corresponding dimensions
- * of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it is entirely contained within the {@link ViewPort} and center it.
+ * <p>
+ * Both dimensions of the {@link ViewPort} will be equal or less than the corresponding
+ * dimensions of the output.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FIT_CENTER = 4;
/**
- * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
- * within the container, and align it to the bottom right corner of the view.
- * Both dimensions of the preview will be equal or less than the corresponding dimensions
- * of its container.
- *
- * @hide
+ * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+ * ratio, so it is entirely contained within the {@link ViewPort}, and align it to the end of
+ * the {@link ViewPort}, which is the bottom right corner in a left-to-right (LTR) layout, or
+ * the bottom left corner in a right-to-left (RTL) layout.
+ * <p>
+ * Both dimensions of the {@link ViewPort} will be equal or less than the corresponding
+ * dimensions of the output.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FIT_END = 5;
@ScaleType
@@ -146,32 +142,23 @@
/**
* Gets the aspect ratio of the {@link ViewPort}.
- *
- * @hide
*/
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Rational getAspectRatio() {
return mAspectRatio;
}
/**
* Gets the rotation of the {@link ViewPort}.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ImageOutputConfig.RotationValue
public int getRotation() {
return mRotation;
}
/**
- * Gets the {@link ScaleType} of the {@link ViewPort}.
- *
- * @hide
+ * Gets the scale type of the {@link ViewPort}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ScaleType
public int getScaleType() {
return mScaleType;
@@ -190,10 +177,8 @@
/**
* Builder for {@link ViewPort}.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroup
public static class Builder {
private static final int DEFAULT_LAYOUT_DIRECTION = android.util.LayoutDirection.LTR;
@@ -212,6 +197,18 @@
private int mLayoutDirection = DEFAULT_LAYOUT_DIRECTION;
+ /**
+ * Creates {@link ViewPort.Builder} with aspect ratio and rotation
+ *
+ * <p> The rotation value is relative to the "natural" rotation. This is one of four
+ * valid values: {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+ * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. For example, if the desired
+ * the orientation is the gravity, the value should be set with the value
+ * of {@link Display#getRotation()}.
+ *
+ * <p> The aspect ratio is the desired crop rect after rotation is applied. In practice,
+ * this is usually the aspect ratio of the view finder.
+ */
public Builder(@NonNull Rational aspectRatio,
@ImageOutputConfig.RotationValue int rotation) {
mAspectRatio = aspectRatio;
@@ -221,13 +218,14 @@
/**
* Sets the {@link ScaleType} of the {@link ViewPort}.
*
- * <p> The value is used by {@link UseCase} to calculate the crop rect. The default value is
- * {@link #FILL_CENTER} if not set.
+ * <p> The value is used by {@link UseCase} to calculate the
+ * {@link UseCase#getViewPortCropRect()}.
+ *
+ * <p> The default value is {@link #FILL_CENTER} if not set.
*
* @hide
*/
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Builder setScaleType(@ScaleType int scaleType) {
mScaleType = scaleType;
return this;
@@ -245,7 +243,6 @@
* @hide
*/
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Builder setLayoutDirection(@LayoutDirection int layoutDirection) {
mLayoutDirection = layoutDirection;
return this;
@@ -253,10 +250,7 @@
/**
* Builds the {@link ViewPort}.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
public ViewPort build() {
Preconditions.checkNotNull(mAspectRatio, "The crop aspect ratio must be set.");
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 1346c52..ae1a46b 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
@@ -23,6 +23,7 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.UseCase;
import androidx.camera.core.ViewPort;
import androidx.camera.core.impl.CameraControlInternal;
@@ -108,6 +109,7 @@
* @throws CameraException Thrown if the combination of newly attached UseCases and the
* currently attached UseCases exceed the capability of the camera.
*/
+ @UseExperimental(markerClass = androidx.camera.core.ExperimentalUseCaseGroup.class)
public void attachUseCases(@NonNull Collection<UseCase> useCases) throws CameraException {
synchronized (mLock) {
List<UseCase> useCaseListAfterUpdate = new ArrayList<>(mAttachedUseCases);
diff --git a/camera/camera-lifecycle/api/1.0.0-beta06.txt b/camera/camera-lifecycle/api/1.0.0-beta06.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/1.0.0-beta06.txt
@@ -2,7 +2,7 @@
package androidx.camera.lifecycle {
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/current.txt b/camera/camera-lifecycle/api/current.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/current.txt
+++ b/camera/camera-lifecycle/api/current.txt
@@ -2,7 +2,7 @@
package androidx.camera.lifecycle {
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt b/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
index 777baf2..b28a42b 100644
--- a/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
@@ -4,8 +4,12 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroupLifecycle {
+ }
+
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) @androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
diff --git a/camera/camera-lifecycle/api/public_plus_experimental_current.txt b/camera/camera-lifecycle/api/public_plus_experimental_current.txt
index 777baf2..b28a42b 100644
--- a/camera/camera-lifecycle/api/public_plus_experimental_current.txt
+++ b/camera/camera-lifecycle/api/public_plus_experimental_current.txt
@@ -4,8 +4,12 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroupLifecycle {
+ }
+
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) @androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
diff --git a/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt b/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
@@ -2,7 +2,7 @@
package androidx.camera.lifecycle {
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/restricted_current.txt b/camera/camera-lifecycle/api/restricted_current.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/restricted_current.txt
+++ b/camera/camera-lifecycle/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.camera.lifecycle {
public final class ProcessCameraProvider {
- method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java
new file mode 100644
index 0000000..9b4891b
--- /dev/null
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java
@@ -0,0 +1,40 @@
+/*
+ * 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.camera.lifecycle;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Same as {@link androidx.camera.core.ExperimentalUseCaseGroup}. Should only be used in
+ * lifecycle artifact.
+ *
+ * <p> The duplication is to workaround an issue where experimental annotation from a
+ * different artifact cannot be properly applied. Adding this annotation hides the API from
+ * current.txt.
+ *
+ * @see androidx.camera.core.ExperimentalUseCaseGroup
+ *
+ * TODO(b/159033688): Remove after the bug is fixed.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalUseCaseGroupLifecycle {
+}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index b1d9283..858b1a2 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -24,11 +24,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraXConfig;
+import androidx.camera.core.ExperimentalUseCaseGroup;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.InitializationException;
@@ -246,6 +248,7 @@
@SuppressWarnings("lambdaLast")
@MainThread
@NonNull
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@NonNull UseCase... useCases) {
@@ -262,19 +265,18 @@
*
* <p> If one {@link UseCase} is in multiple {@link UseCaseGroup}s, it will be linked to
* the {@link UseCaseGroup} in the latest
- * {@code #bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup)} call.
- *
- * @hide
+ * {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup)} call.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroupLifecycle
@SuppressWarnings("lambdaLast")
@MainThread
@NonNull
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@NonNull UseCaseGroup useCaseGroup) {
return CameraX.bindToLifecycle(lifecycleOwner, cameraSelector,
- useCaseGroup.getViewPort(), useCaseGroup.getUseCases());
+ useCaseGroup.getViewPort(), useCaseGroup.getUseCases().toArray(new UseCase[0]));
}
@Override
diff --git a/compose/compose-runtime/api/api_lint.ignore b/compose/compose-runtime/api/api_lint.ignore
index 37cdb9f..215da53 100644
--- a/compose/compose-runtime/api/api_lint.ignore
+++ b/compose/compose-runtime/api/api_lint.ignore
@@ -75,3 +75,8 @@
Should avoid odd sized primitives; use `int` instead of `byte` in parameter value in androidx.compose.Composer.changed(byte value)
NoByteOrShort: androidx.compose.Composer#changed(short) parameter #0:
Should avoid odd sized primitives; use `int` instead of `short` in parameter value in androidx.compose.Composer.changed(short value)
+
+NotCloseable: androidx.compose.SlotReader:
+ Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.compose.SlotReader
+NotCloseable: androidx.compose.SlotWriter:
+ Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.compose.SlotWriter
diff --git a/concurrent/futures/api/api_lint.ignore b/concurrent/futures/api/api_lint.ignore
index 2539658..41ee2724 100644
--- a/concurrent/futures/api/api_lint.ignore
+++ b/concurrent/futures/api/api_lint.ignore
@@ -3,5 +3,9 @@
Methods must not throw generic exceptions (`java.lang.Exception`)
+NotCloseable: androidx.concurrent.futures.CallbackToFutureAdapter.Completer:
+ Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class androidx.concurrent.futures.CallbackToFutureAdapter.Completer
+
+
PairedRegistration: androidx.concurrent.futures.CallbackToFutureAdapter.Completer#addCancellationListener(Runnable, java.util.concurrent.Executor):
Found addCancellationListener but not removeCancellationListener in androidx.concurrent.futures.CallbackToFutureAdapter.Completer
diff --git a/development/build_log_simplifier.py b/development/build_log_simplifier.py
index b3cceb7..b7f5cf2 100755
--- a/development/build_log_simplifier.py
+++ b/development/build_log_simplifier.py
@@ -69,6 +69,48 @@
prev_line_is_boring = False
return result
+def remove_known_uninteresting_lines(lines):
+ skipLines = {
+ "A fine-grained performance profile is available: use the --scan option.",
+ "* Get more help at https://help.gradle.org",
+ "Use '--warning-mode all' to show the individual deprecation warnings.",
+ "See https://docs.gradle.org/6.5/userguide/command_line_interface.html#sec:command_line_warnings"
+
+ "Note: Some input files use or override a deprecated API.",
+ "Note: Recompile with -Xlint:deprecation for details."
+ }
+ skipPrefixes = [
+ "See the profiling report at:",
+
+ "Deprecated Gradle features were used in this build"
+ ]
+ result = []
+ for line in lines:
+ stripped = line.strip()
+ if stripped in skipLines:
+ continue
+ include = True
+ for prefix in skipPrefixes:
+ if stripped.startswith(prefix):
+ include = False
+ break
+ if include:
+ result.append(line)
+ return result
+
+def collapse_consecutive_blank_lines(lines):
+ result = []
+ prev_blank = False
+ for line in lines:
+ if line.strip() == "":
+ if not prev_blank:
+ result.append(line)
+ prev_blank = True
+ else:
+ result.append(line)
+ prev_blank = False
+ return result
+
# If multiple tasks have no output, this function removes all but the first and last
# For example, turns this:
# > Task :a
@@ -79,13 +121,19 @@
# > Task :a
# > Task ...
# > Task :d
-def skip_tasks_having_no_output(lines):
+def collapse_tasks_having_no_output(lines):
result = []
pending_tasks = []
- for line in lines + [""]:
+ for line in lines:
is_task = line.startswith("> Task ")
if is_task:
pending_tasks.append(line)
+ elif line.strip() == "":
+ # If only blank lines occur between tasks, skip those blank lines
+ if len(pending_tasks) > 0:
+ pending_tasks.append(line)
+ else:
+ result.append(line)
else:
if len(pending_tasks) > 0:
result += pending_tasks[0]
@@ -94,8 +142,7 @@
if len(pending_tasks) > 1:
result += pending_tasks[-1]
pending_tasks = []
- if line != "":
- result.append(line)
+ result.append(line)
return result
try:
@@ -107,7 +154,9 @@
lines = select_failing_task_output(lines)
lines = shorten_uninteresting_stack_frames(lines)
- lines = skip_tasks_having_no_output(lines)
+ lines = remove_known_uninteresting_lines(lines)
+ lines = collapse_consecutive_blank_lines(lines)
+ lines = collapse_tasks_having_no_output(lines)
print(len(lines))
print(''.join(lines))
diff --git a/development/build_log_simplifier.sh b/development/build_log_simplifier.sh
new file mode 100755
index 0000000..e2093bc
--- /dev/null
+++ b/development/build_log_simplifier.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -e
+
+usage() {
+ echo "usage: $0 <command> <arguments>"
+ echo
+ echo "executes <command> <arguments> and then runs build_log_simplifier.py against its output"
+ exit 1
+}
+
+if [[ "$1" == "" ]]; then
+ usage
+fi
+
+# run Gradle and save stdout and stderr into $logFile
+SCRIPT_PATH="$(cd $(dirname $0) && pwd)"
+if [ -n "$DIST_DIR" ]; then
+ LOG_DIR="$DIST_DIR"
+else
+ LOG_DIR="$SCRIPT_PATH/../../../out/dist"
+fi
+
+mkdir -p "$LOG_DIR"
+logFile="$LOG_DIR/gradle.log"
+rm -f "$logFile"
+echo "Running $@"
+if bash -c "$*" > >(tee -a "$logFile") 2> >(tee -a "$logFile" >&2); then
+ echo "Succeeded: $*"
+else
+ echo >&2
+ echo "Failed: $*" >&2
+ echo Attempting to locate the relevant error messages via build_log_simplifier.py >&2
+ echo >&2
+ # Try to identify the most relevant lines of output, and put them at the bottom of the
+ # output where they will also be placed into the build failure email.
+ # TODO: We may be able to stop cleaning up Gradle's output after Gradle can do this on its own:
+ # https://github.com/gradle/gradle/issues/1005
+ # and https://github.com/gradle/gradle/issues/13090
+ summaryLog="$LOG_DIR/error_summary.log"
+ $SCRIPT_PATH/build_log_simplifier.py $logFile | tail -n 100 | tee "$summaryLog" >&2
+ exit 1
+fi
diff --git a/gradlew b/gradlew
index 49facc3..db30cc3 100755
--- a/gradlew
+++ b/gradlew
@@ -240,49 +240,15 @@
fi
}
-# Runs a build and possibly modifies the output before displaying it, to make it easier to
-# interpret, and to make the relevant error messages fit inside of build failure emails.
-function runBuild() {
- if [[ " ${@} " =~ " -Pandroidx.summarizeStderr " ]]; then
- # run Gradle and save stdout and stderr into $logFile
- if [ -n "$DIST_DIR" ]; then
- LOG_DIR="$DIST_DIR"
- else
- LOG_DIR="$OUT_DIR/dist"
- fi
- mkdir -p "$LOG_DIR"
- logFile="$LOG_DIR/gradle.log"
- rm -f "$logFile"
- if runGradle "$@" > >(tee -a "$logFile") 2> >(tee -a "$logFile" >&2); then
- echo Gradle success
- else
- echo >&2
- echo Gradle failure >&2
- echo Attempting to locate the relevant error messages via build_log_simplifier.py >&2
- echo >&2
- # Try to identify the most relevant lines of output, and put them at the bottom of the
- # output where they will also be placed into the build failure email.
- # TODO: We may be able to stop cleaning up Gradle's output after Gradle can do this on its own:
- # https://github.com/gradle/gradle/issues/1005
- # and https://github.com/gradle/gradle/issues/13090
- summaryLog="$LOG_DIR/error_summary.log"
- $SCRIPT_PATH/development/build_log_simplifier.py $logFile | tail -n 100 | tee "$summaryLog" >&2
- return 1
- fi
- else
- runGradle "$@"
- fi
-}
-
if [[ " ${@} " =~ " -PdisallowExecution " ]]; then
echo "Passing '-PdisallowExecution' directly is forbidden. Did you mean -PverifyUpToDate ?"
echo "See TaskUpToDateValidator.java for more information"
exit 1
fi
-runBuild "$@"
+runGradle "$@"
# Check whether we were given the "-PverifyUpToDate" argument
if [[ " ${@} " =~ " -PverifyUpToDate " ]]; then
# Re-run Gradle, and find all tasks that are unexpectly out of date
- runBuild "$@" -PdisallowExecution --continue --info
+ runGradle "$@" -PdisallowExecution --continue --info
fi
diff --git a/media/media/api/1.2.0-alpha03.txt b/media/media/api/1.2.0-alpha03.txt
index 1f73794..72d9f06 100644
--- a/media/media/api/1.2.0-alpha03.txt
+++ b/media/media/api/1.2.0-alpha03.txt
@@ -593,8 +593,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index 1f73794..72d9f06 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -593,8 +593,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/public_plus_experimental_1.2.0-alpha03.txt b/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
index c845f56..db47a1b 100644
--- a/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
+++ b/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
@@ -593,8 +593,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index c845f56..db47a1b 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -593,8 +593,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/restricted_1.2.0-alpha03.txt b/media/media/api/restricted_1.2.0-alpha03.txt
index c14e757..37b65c8 100644
--- a/media/media/api/restricted_1.2.0-alpha03.txt
+++ b/media/media/api/restricted_1.2.0-alpha03.txt
@@ -616,8 +616,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
@@ -680,9 +680,11 @@
public abstract class VolumeProviderCompat {
ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int, String?);
method public final int getCurrentVolume();
method public final int getMaxVolume();
method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final String? getVolumeControlId();
method public Object! getVolumeProvider();
method public void onAdjustVolume(int);
method public void onSetVolumeTo(int);
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index c14e757..37b65c8 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -616,8 +616,8 @@
public final class AudioManagerCompat {
method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
- method public static int getStreamMaxVolume(android.media.AudioManager, int);
- method public static int getStreamMinVolume(android.media.AudioManager, int);
+ method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+ method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
@@ -680,9 +680,11 @@
public abstract class VolumeProviderCompat {
ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int, String?);
method public final int getCurrentVolume();
method public final int getMaxVolume();
method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final String? getVolumeControlId();
method public Object! getVolumeProvider();
method public void onAdjustVolume(int);
method public void onSetVolumeTo(int);
diff --git a/media/media/src/main/java/androidx/media/AudioManagerCompat.java b/media/media/src/main/java/androidx/media/AudioManagerCompat.java
index 6175267..a6d4ae7 100644
--- a/media/media/src/main/java/androidx/media/AudioManagerCompat.java
+++ b/media/media/src/main/java/androidx/media/AudioManagerCompat.java
@@ -19,7 +19,9 @@
import android.media.AudioManager;
import android.os.Build;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat.StreamType;
/** Compatibility library for {@link AudioManager} with fallbacks for older platforms. */
public final class AudioManagerCompat {
@@ -127,7 +129,9 @@
* @param streamType The stream type whose maximum volume index is returned.
* @return The maximum valid volume index for the stream.
*/
- public static int getStreamMaxVolume(@NonNull AudioManager audioManager, int streamType) {
+ @IntRange(from = 0)
+ public static int getStreamMaxVolume(@NonNull AudioManager audioManager,
+ @StreamType int streamType) {
return audioManager.getStreamMaxVolume(streamType);
}
@@ -137,7 +141,9 @@
* @param streamType The stream type whose minimum volume index is returned.
* @return The minimum valid volume index for the stream.
*/
- public static int getStreamMinVolume(@NonNull AudioManager audioManager, int streamType) {
+ @IntRange(from = 0)
+ public static int getStreamMinVolume(@NonNull AudioManager audioManager,
+ @StreamType int streamType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return audioManager.getStreamMinVolume(streamType);
} else {
diff --git a/media/media/src/main/java/androidx/media/VolumeProviderCompat.java b/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
index 6d835d6..201d74e 100644
--- a/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
+++ b/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
@@ -23,6 +23,7 @@
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import java.lang.annotation.Retention;
@@ -66,6 +67,7 @@
private final int mControlType;
private final int mMaxVolume;
+ private final String mControlId;
private int mCurrentVolume;
private Callback mCallback;
@@ -81,9 +83,26 @@
* @param currentVolume The current volume.
*/
public VolumeProviderCompat(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+ this(volumeControl, maxVolume, currentVolume, null);
+ }
+
+ /**
+ * Create a new volume provider for handling volume events. You must specify
+ * the type of volume control and the maximum volume that can be used.
+ *
+ * @param volumeControl The method for controlling volume that is used by this provider.
+ * @param maxVolume The maximum allowed volume.
+ * @param currentVolume The current volume.
+ * @param volumeControlId The volume control ID of this provider.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ public VolumeProviderCompat(@ControlType int volumeControl, int maxVolume, int currentVolume,
+ @Nullable String volumeControlId) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
mCurrentVolume = currentVolume;
+ mControlId = volumeControlId;
}
/**
@@ -132,6 +151,19 @@
}
/**
+ * Gets the volume control ID. It can be used to identify which volume provider is
+ * used by the session.
+ *
+ * @return the volume control ID or {@code null} if it isn't set.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @Nullable
+ public final String getVolumeControlId() {
+ return mControlId;
+ }
+
+ /**
* Override to handle requests to set the volume of the current output.
*
* @param volume The volume to set the output to.
@@ -166,18 +198,33 @@
* @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
*/
public Object getVolumeProvider() {
- if (mVolumeProviderFwk == null && Build.VERSION.SDK_INT >= 21) {
- mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
- @Override
- public void onSetVolumeTo(int volume) {
- VolumeProviderCompat.this.onSetVolumeTo(volume);
- }
+ if (mVolumeProviderFwk == null) {
+ if (Build.VERSION.SDK_INT >= 30) {
+ mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume,
+ mControlId) {
+ @Override
+ public void onSetVolumeTo(int volume) {
+ VolumeProviderCompat.this.onSetVolumeTo(volume);
+ }
- @Override
- public void onAdjustVolume(int direction) {
- VolumeProviderCompat.this.onAdjustVolume(direction);
- }
- };
+ @Override
+ public void onAdjustVolume(int direction) {
+ VolumeProviderCompat.this.onAdjustVolume(direction);
+ }
+ };
+ } else if (Build.VERSION.SDK_INT >= 21) {
+ mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
+ @Override
+ public void onSetVolumeTo(int volume) {
+ VolumeProviderCompat.this.onSetVolumeTo(volume);
+ }
+
+ @Override
+ public void onAdjustVolume(int direction) {
+ VolumeProviderCompat.this.onAdjustVolume(direction);
+ }
+ };
+ }
}
return mVolumeProviderFwk;
}
diff --git a/media2/common/build.gradle b/media2/common/build.gradle
index a178d30..ab02530 100644
--- a/media2/common/build.gradle
+++ b/media2/common/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api(project(":media:media"))
+ api("androidx.media:media:1.2.0-alpha03")
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.concurrent:concurrent-futures:1.0.0")
implementation("androidx.collection:collection:1.0.0")
diff --git a/media2/player/build.gradle b/media2/player/build.gradle
index 52cad9b..3ef2668 100644
--- a/media2/player/build.gradle
+++ b/media2/player/build.gradle
@@ -10,7 +10,6 @@
}
dependencies {
- api(project(":media:media"))
api(project(":media2:media2-common"))
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/media2/session/build.gradle b/media2/session/build.gradle
index 0a6a8e8..ce7f481 100644
--- a/media2/session/build.gradle
+++ b/media2/session/build.gradle
@@ -10,7 +10,6 @@
}
dependencies {
- api(project(":media:media"))
api(project(":media2:media2-common"))
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/media2/widget/build.gradle b/media2/widget/build.gradle
index f17f115..e298d01 100644
--- a/media2/widget/build.gradle
+++ b/media2/widget/build.gradle
@@ -25,7 +25,6 @@
}
dependencies {
- api(project(":media2:media2-common"))
api(project(":media2:media2-session"))
implementation("androidx.appcompat:appcompat:1.0.2")
implementation("androidx.palette:palette:1.0.0")
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 23bc80e..aec35b5 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -29,18 +29,18 @@
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.atomic.AtomicBoolean
/** @suppress */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract class PagingDataDiffer<T : Any>(
private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) {
- private val collecting = AtomicBoolean(false)
private var presenter: PagePresenter<T> = PagePresenter.initial()
private var receiver: UiReceiver? = null
private val dataRefreshedListeners: MutableList<() -> Unit> = CopyOnWriteArrayList()
+ private val collectFromRunner = SingleRunner()
+
@Volatile
private var lastAccessedIndex: Int = 0
@@ -58,53 +58,47 @@
open fun postEvents(): Boolean = false
- suspend fun collectFrom(pagingData: PagingData<T>, callback: PresenterCallback) {
- check(collecting.compareAndSet(false, true)) {
- "Collecting from multiple PagingData concurrently is an illegal operation."
- }
-
+ suspend fun collectFrom(
+ pagingData: PagingData<T>,
+ callback: PresenterCallback
+ ) = collectFromRunner.runInIsolation {
receiver = pagingData.receiver
- try {
- pagingData.flow
- .collect { event ->
- withContext(mainDispatcher) {
- if (event is PageEvent.Insert && event.loadType == REFRESH) {
- val newPresenter = PagePresenter(event)
- val transformedLastAccessedIndex = performDiff(
- previousList = presenter,
- newList = newPresenter,
- newCombinedLoadStates = event.combinedLoadStates,
- lastAccessedIndex = lastAccessedIndex
- )
- presenter = newPresenter
+ pagingData.flow.collect { event ->
+ withContext(mainDispatcher) {
+ if (event is PageEvent.Insert && event.loadType == REFRESH) {
+ val newPresenter = PagePresenter(event)
+ val transformedLastAccessedIndex = performDiff(
+ previousList = presenter,
+ newList = newPresenter,
+ newCombinedLoadStates = event.combinedLoadStates,
+ lastAccessedIndex = lastAccessedIndex
+ )
+ presenter = newPresenter
- // Dispatch ListUpdate as soon as we are done diffing.
- dataRefreshedListeners.forEach { listener -> listener() }
+ // Dispatch ListUpdate as soon as we are done diffing.
+ dataRefreshedListeners.forEach { listener -> listener() }
- // Transform the last loadAround index from the old list to the new list
- // by passing it through the DiffResult, and pass it forward as a
- // ViewportHint within the new list to the next generation of Pager.
- // This ensures prefetch distance for the last ViewportHint from the old
- // list is respected in the new list, even if invalidation interrupts
- // the prepend / append load that would have fulfilled it in the old
- // list.
- transformedLastAccessedIndex?.let { newIndex ->
- lastAccessedIndex = newIndex
- receiver?.addHint(presenter.loadAround(newIndex))
- }
- } else {
- if (postEvents()) {
- yield()
- }
-
- // Send event to presenter to be shown to the UI.
- presenter.processEvent(event, callback)
- }
+ // Transform the last loadAround index from the old list to the new list
+ // by passing it through the DiffResult, and pass it forward as a
+ // ViewportHint within the new list to the next generation of Pager.
+ // This ensures prefetch distance for the last ViewportHint from the old
+ // list is respected in the new list, even if invalidation interrupts
+ // the prepend / append load that would have fulfilled it in the old
+ // list.
+ transformedLastAccessedIndex?.let { newIndex ->
+ lastAccessedIndex = newIndex
+ receiver?.addHint(presenter.loadAround(newIndex))
}
+ } else {
+ if (postEvents()) {
+ yield()
+ }
+
+ // Send event to presenter to be shown to the UI.
+ presenter.processEvent(event, callback)
}
- } finally {
- collecting.set(false)
+ }
}
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt b/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt
new file mode 100644
index 0000000..e575e6e
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.paging
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/**
+ * Class which guarantees single execution of blocks passed to [runInIsolation] by cancelling the
+ * previous call. [runInIsolation] is backed by a [Mutex], which is fair, so concurrent callers of
+ * [runInIsolation] will trigger in order, with the last call winning (by cancelling previous calls)
+ */
+internal class SingleRunner {
+ private val mutex = Mutex()
+ private var previous: CoroutineScope? = null
+
+ suspend fun runInIsolation(block: suspend () -> Unit) {
+ coroutineScope {
+ mutex.withLock {
+ previous?.cancel()
+ previous = this
+ }
+ try {
+ block()
+ } finally {
+ mutex.withLock {
+ if (previous == this) {
+ previous = null
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
index 764f791..74f077e 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
@@ -45,8 +45,8 @@
import org.junit.runners.JUnit4
import kotlin.coroutines.ContinuationInterceptor
import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
+import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
@RunWith(JUnit4::class)
@@ -123,14 +123,20 @@
fun collectFrom_twiceConcurrently() = testScope.runBlockingTest {
val differ = SimpleDiffer()
- val job = launch {
- differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
- }
- assertFailsWith<IllegalStateException> {
+ val job1 = launch {
differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
}
- job.cancel()
+ // Ensure job1 is running.
+ assertTrue { job1.isActive }
+
+ val job2 = launch {
+ differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
+ }
+
+ // job2 collection should cancel job1.
+ assertTrue { job1.isCancelled }
+ job2.cancel()
}
@Test
diff --git a/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt b/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt
new file mode 100644
index 0000000..d1ff12b9
--- /dev/null
+++ b/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 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.
+ */
+
+@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+
+package androidx.paging
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+class SingleRunnerTest {
+ private val testScope = TestCoroutineScope()
+
+ @Test
+ fun cancelsPreviousRun() = runBlocking {
+ val runner = SingleRunner()
+ val job = launch(Dispatchers.Unconfined) {
+ runner.runInIsolation {
+ delay(Long.MAX_VALUE)
+ }
+ }
+
+ runner.runInIsolation {
+ // Immediately return.
+ }
+
+ assertTrue { job.isCancelled }
+ }
+
+ @Test
+ fun preventsCompletionUntilBlockCompletes() = testScope.runBlockingTest {
+ val runner = SingleRunner()
+ val job = testScope.launch {
+ runner.runInIsolation {
+ delay(1000)
+ }
+ }
+
+ advanceTimeBy(500)
+ assertFalse { job.isCompleted }
+
+ advanceTimeBy(500)
+ assertTrue { job.isCompleted }
+ }
+
+ @Test
+ fun orderedExecution() = testScope.runBlockingTest {
+ val jobStartList = mutableListOf<Int>()
+
+ val runner = SingleRunner()
+ for (index in 0..9) {
+ launch {
+ runner.runInIsolation {
+ jobStartList.add(index)
+ delay(Long.MAX_VALUE)
+ }
+ }
+ }
+
+ // Cancel previous job.
+ runner.runInIsolation {
+ // Immediately return.
+ }
+
+ assertEquals(List(10) { it }, jobStartList)
+ }
+}
\ No newline at end of file
diff --git a/paging/runtime/api/3.0.0-alpha02.txt b/paging/runtime/api/3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/3.0.0-alpha02.txt
+++ b/paging/runtime/api/3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/api_lint.ignore b/paging/runtime/api/api_lint.ignore
index 45e4c89..22d1e0a 100644
--- a/paging/runtime/api/api_lint.ignore
+++ b/paging/runtime/api/api_lint.ignore
@@ -7,7 +7,9 @@
ListenerLast: androidx.paging.AsyncPagedListDiffer#AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T>) parameter #1:
Listeners should always be at end of argument list (method `AsyncPagedListDiffer`)
-
-
-TopLevelBuilder: androidx.paging.LivePagedListBuilder:
- Builder should be defined as inner class: androidx.paging.LivePagedListBuilder
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher) parameter #2:
+ Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher) parameter #2:
+ Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher) parameter #3:
+ Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt b/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
+++ b/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/public_plus_experimental_current.txt b/paging/runtime/api/public_plus_experimental_current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/public_plus_experimental_current.txt
+++ b/paging/runtime/api/public_plus_experimental_current.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/restricted_3.0.0-alpha02.txt b/paging/runtime/api/restricted_3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/restricted_3.0.0-alpha02.txt
+++ b/paging/runtime/api/restricted_3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -24,7 +24,9 @@
}
public final class AsyncPagingDataDiffer<T> {
- ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+ ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 42ae07a..68b70f3 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -31,8 +31,10 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Runnable
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
@@ -41,6 +43,8 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+import kotlin.test.assertEquals
import kotlin.test.assertTrue
private class ListUpdateCapture : ListUpdateCallback {
@@ -348,4 +352,83 @@
job2.cancel()
}
}
+
+ @Test
+ fun submitData_guaranteesOrder() = testScope.runBlockingTest {
+ val pager = Pager(config = PagingConfig(2, enablePlaceholders = false), initialKey = 50) {
+ TestPagingSource()
+ }
+
+ val reversedDispatcher = object : CoroutineDispatcher() {
+ var lastBlock: Runnable? = null
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ // Save the first block to be dispatched, then run second one first after receiving
+ // calls to dispatch both.
+ val lastBlock = lastBlock
+ if (lastBlock == null) {
+ this.lastBlock = block
+ } else {
+ block.run()
+ lastBlock.run()
+ }
+ }
+ }
+
+ val lifecycle = TestLifecycleOwner()
+ differ.submitData(lifecycle.lifecycle, PagingData.empty())
+ differ.submitData(lifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+
+ // Ensure the second call wins when dispatched in order of execution.
+ advanceUntilIdle()
+ assertEquals(6, differ.itemCount)
+
+ val reversedLifecycle = TestLifecycleOwner(coroutineDispatcher = reversedDispatcher)
+ differ.submitData(reversedLifecycle.lifecycle, PagingData.empty())
+ differ.submitData(reversedLifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+
+ // Ensure the second call wins when dispatched in reverse order of execution.
+ advanceUntilIdle()
+ assertEquals(6, differ.itemCount)
+ }
+
+ @Test
+ fun submitData_cancelsLastSuspendSubmit() = testScope.runBlockingTest {
+ pauseDispatcher {
+ val pager = Pager(
+ config = PagingConfig(2),
+ initialKey = 50
+ ) { TestPagingSource() }
+ val pager2 = Pager(
+ config = PagingConfig(2),
+ initialKey = 50
+ ) { TestPagingSource() }
+
+ val lifecycle = TestLifecycleOwner()
+ var jobSubmitted = false
+ val job = launch {
+ pager.flow.collectLatest {
+ jobSubmitted = true
+ differ.submitData(it)
+ }
+ }
+
+ advanceUntilIdle()
+
+ var job2Submitted = false
+ val job2 = launch {
+ pager2.flow.collectLatest {
+ job2Submitted = true
+ differ.submitData(lifecycle.lifecycle, it)
+ }
+ }
+
+ advanceUntilIdle()
+
+ assertTrue(jobSubmitted)
+ assertTrue(job2Submitted)
+
+ job.cancel()
+ job2.cancel()
+ }
+ }
}
\ No newline at end of file
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index cf3ce31..dd032e5 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -22,19 +22,16 @@
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.atomic.AtomicReference
+import java.util.concurrent.atomic.AtomicInteger
/**
* Helper class for mapping a [PagingData] into a
@@ -44,11 +41,11 @@
* [AsyncPagingDataDiffer] is exposed for complex cases, and where overriding [PagingDataAdapter] to
* support paging isn't convenient.
*/
-class AsyncPagingDataDiffer<T : Any>(
- private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
- private val workerDispatcher: CoroutineDispatcher = Dispatchers.Default,
+class AsyncPagingDataDiffer<T : Any> @JvmOverloads constructor(
private val diffCallback: DiffUtil.ItemCallback<T>,
- private val updateCallback: ListUpdateCallback
+ private val updateCallback: ListUpdateCallback,
+ private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
+ private val workerDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
internal val callback = object : PresenterCallback {
override fun onInserted(position: Int, count: Int) {
@@ -139,7 +136,7 @@
}
}
- private val job = AtomicReference<Job?>(null)
+ private val submitDataId = AtomicInteger(0)
/**
* Present a [PagingData] until it is invalidated by a call to [refresh] or
@@ -163,11 +160,8 @@
* @see [Pager]
*/
suspend fun submitData(pagingData: PagingData<T>) {
- try {
- job.get()?.cancelAndJoin()
- } finally {
- differBase.collectFrom(pagingData, callback)
- }
+ submitDataId.incrementAndGet()
+ differBase.collectFrom(pagingData, callback)
}
/**
@@ -183,16 +177,14 @@
* @see [Pager]
*/
fun submitData(lifecycle: Lifecycle, pagingData: PagingData<T>) {
- var oldJob: Job?
- var newJob: Job
- do {
- oldJob = job.get()
- newJob = lifecycle.coroutineScope.launch(start = CoroutineStart.LAZY) {
- oldJob?.cancelAndJoin()
+ val id = submitDataId.incrementAndGet()
+ lifecycle.coroutineScope.launch {
+ // Check id when this job runs to ensure the last synchronous call submitData always
+ // wins.
+ if (submitDataId.get() == id) {
differBase.collectFrom(pagingData, callback)
}
- } while (!job.compareAndSet(oldJob, newJob))
- newJob.start()
+ }
}
/**
diff --git a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
index 0baa40c..a6cbdd0 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
@@ -47,16 +47,16 @@
*
* @sample androidx.paging.samples.pagingDataAdapterSample
*/
-abstract class PagingDataAdapter<T : Any, VH : RecyclerView.ViewHolder>(
+abstract class PagingDataAdapter<T : Any, VH : RecyclerView.ViewHolder> @JvmOverloads constructor(
diffCallback: DiffUtil.ItemCallback<T>,
mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
workerDispatcher: CoroutineDispatcher = Dispatchers.Default
) : RecyclerView.Adapter<VH>() {
private val differ = AsyncPagingDataDiffer(
- mainDispatcher = mainDispatcher,
- workerDispatcher = workerDispatcher,
diffCallback = diffCallback,
- updateCallback = AdapterListUpdateCallback(this)
+ updateCallback = AdapterListUpdateCallback(this),
+ mainDispatcher = mainDispatcher,
+ workerDispatcher = workerDispatcher
)
/**
@@ -170,6 +170,7 @@
* @param listener [LoadStates] listener to receive updates.
*
* @see removeLoadStateListener
+ * @sample androidx.paging.samples.addLoadStateListenerSample
*/
fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
differ.addLoadStateListener(listener)
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index 7a5e169..97f1428 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -14,10 +14,8 @@
api("androidx.annotation:annotation:1.1.0")
api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
- compileOnly(project(":fragment:fragment"))
+ compileOnly("androidx.fragment:fragment:1.2.5")
compileOnly("androidx.appcompat:appcompat:1.0.1")
- // necessary for IJ to resolve dependencies.
- implementation(project(":lifecycle:lifecycle-runtime"))
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
index 1417f3d..ec407be 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
@@ -16,7 +16,6 @@
package androidx.ui.benchmark.test.autofill
-import android.graphics.Rect
import android.util.SparseArray
import android.view.View
import android.view.autofill.AutofillValue
@@ -24,12 +23,13 @@
import androidx.benchmark.junit4.measureRepeated
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.LargeTest
-import androidx.ui.autofill.AutofillNode
import androidx.test.filters.SdkSuppress
+import androidx.ui.autofill.AutofillNode
import androidx.ui.autofill.AutofillTree
import androidx.ui.autofill.AutofillType
import androidx.ui.core.AutofillTreeAmbient
import androidx.ui.core.ViewAmbient
+import androidx.ui.geometry.Rect
import androidx.ui.test.createComposeRule
import org.junit.Before
import org.junit.Rule
@@ -67,7 +67,7 @@
val autofillNode = AutofillNode(
onFill = {},
autofillTypes = listOf(AutofillType.PersonFullName),
- boundingBox = Rect(0, 0, 0, 0)
+ boundingBox = Rect(0f, 0f, 0f, 0f)
)
val autofillValues = SparseArray<AutofillValue>().apply {
append(autofillNode.id, AutofillValue.forText("Name"))
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
index a5f96bd..e73d55d 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
@@ -81,7 +81,7 @@
onNewState(
TextFieldValue(
text = initText.text,
- selection = TextRange(5, 5)
+ selection = TextRange(5)
),
null, // text input service, not used.
0 // session token, not used
diff --git a/ui/ui-animation-core/api/api_lint.ignore b/ui/ui-animation-core/api/api_lint.ignore
index 3d76ee0..35e0da1 100644
--- a/ui/ui-animation-core/api/api_lint.ignore
+++ b/ui/ui-animation-core/api/api_lint.ignore
@@ -41,5 +41,9 @@
Missing nullability on method `getValue` return
+NotCloseable: androidx.animation.BaseAnimatedValue:
+ Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class androidx.animation.BaseAnimatedValue
+
+
TopLevelBuilder: androidx.animation.AnimationBuilder:
Builder should be defined as inner class: androidx.animation.AnimationBuilder
diff --git a/ui/ui-core/api/0.1.0-dev14.txt b/ui/ui-core/api/0.1.0-dev14.txt
index a640771..db9cb26 100644
--- a/ui/ui-core/api/0.1.0-dev14.txt
+++ b/ui/ui-core/api/0.1.0-dev14.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/api/api_lint.ignore b/ui/ui-core/api/api_lint.ignore
index fbd03c6..83fe9f6 100644
--- a/ui/ui-core/api/api_lint.ignore
+++ b/ui/ui-core/api/api_lint.ignore
@@ -59,5 +59,9 @@
Missing nullability on method `toString-impl` return
+NotCloseable: androidx.ui.core.OwnedLayer:
+ Classes that release resources (destroy()) should implement AutoClosable and CloseGuard: class androidx.ui.core.OwnedLayer
+
+
TopLevelBuilder: androidx.ui.graphics.vector.VectorAssetBuilder:
Builder should be defined as inner class: androidx.ui.graphics.vector.VectorAssetBuilder
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index a640771..db9cb26 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
index 3896319..bfbb96e 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 3896319..bfbb96e 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/api/restricted_0.1.0-dev14.txt b/ui/ui-core/api/restricted_0.1.0-dev14.txt
index 9b29c0d..98633c67 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev14.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 9b29c0d..98633c67 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -24,16 +24,16 @@
}
public final class AutofillNode {
- ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> component1();
- method public android.graphics.Rect? component2();
+ method public androidx.ui.geometry.Rect? component2();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
- method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+ method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
- method public android.graphics.Rect? getBoundingBox();
+ method public androidx.ui.geometry.Rect? getBoundingBox();
method public int getId();
method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
- method public void setBoundingBox(android.graphics.Rect? p);
+ method public void setBoundingBox(androidx.ui.geometry.Rect? p);
property public final int id;
}
diff --git a/ui/ui-core/build.gradle b/ui/ui-core/build.gradle
index 1a54985..2111805 100644
--- a/ui/ui-core/build.gradle
+++ b/ui/ui-core/build.gradle
@@ -40,6 +40,7 @@
implementation(KOTLIN_COROUTINES_CORE)
api project(":ui:ui-unit")
+ api project(":ui:ui-geometry")
api project(":ui:ui-graphics")
api project(":ui:ui-text-core")
api project(":ui:ui-animation-core")
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
index ad62c59..28573ff 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
@@ -27,6 +27,7 @@
import androidx.ui.core.Modifier
import androidx.ui.core.PassThroughLayout
import androidx.ui.core.onChildPositioned
+import androidx.ui.core.toComposeRect
import androidx.ui.foundation.Text
import androidx.ui.foundation.TextField
import androidx.ui.input.ImeAction
@@ -106,7 +107,9 @@
autofillTree += autofillNode
@Suppress("DEPRECATION")
- PassThroughLayout(onChildPositioned { autofillNode.boundingBox = it.boundingBox() }) {
+ PassThroughLayout(onChildPositioned {
+ autofillNode.boundingBox = it.boundingBox().toComposeRect()
+ }) {
children(autofillNode)
}
}
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
index a000527..4ab15c5 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
@@ -16,7 +16,6 @@
package androidx.ui.core
-import android.graphics.Rect
import android.util.SparseArray
import android.view.View
import android.view.ViewStructure
@@ -29,6 +28,7 @@
import androidx.ui.autofill.AutofillNode
import androidx.ui.autofill.AutofillTree
import androidx.ui.autofill.AutofillType
+import androidx.ui.geometry.Rect
import androidx.ui.test.android.fake.FakeViewStructure
import androidx.ui.test.createComposeRule
import com.google.common.truth.Truth.assertThat
@@ -83,7 +83,7 @@
val autofillNode = AutofillNode(
onFill = {},
autofillTypes = listOf(AutofillType.PersonFullName),
- boundingBox = Rect(0, 0, 0, 0)
+ boundingBox = Rect(0f, 0f, 0f, 0f)
)
autofillTree += autofillNode
@@ -112,7 +112,7 @@
val autofillNode = AutofillNode(
onFill = { autofilledValue = it },
autofillTypes = listOf(AutofillType.PersonFullName),
- boundingBox = Rect(0, 0, 0, 0)
+ boundingBox = Rect(0f, 0f, 0f, 0f)
)
val autofillValues = SparseArray<AutofillValue>().apply {
append(autofillNode.id, AutofillValue.forText(expectedValue))
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
index bb6be1d..df06c1f 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
@@ -45,7 +45,7 @@
ic = RecordingInputConnection(
TextFieldValue(
"",
- TextRange(0, 0)
+ TextRange(0)
), listener
)
}
@@ -58,7 +58,7 @@
// Set "Hello, World", and place the cursor at the beginning of the text.
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(0, 0))
+ selection = TextRange(0))
assertEquals("", ic.getTextBeforeCursor(100, 0))
assertEquals("Hello, World", ic.getTextAfterCursor(100, 0))
@@ -66,7 +66,7 @@
// Set "Hello, World", and place the cursor between "H" and "e".
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(1, 1))
+ selection = TextRange(1))
assertEquals("H", ic.getTextBeforeCursor(100, 0))
assertEquals("ello, World", ic.getTextAfterCursor(100, 0))
@@ -74,7 +74,7 @@
// Set "Hello, World", and place the cursor at the end of the text.
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(12, 12))
+ selection = TextRange(12))
assertEquals("Hello, World", ic.getTextBeforeCursor(100, 0))
assertEquals("", ic.getTextAfterCursor(100, 0))
@@ -85,7 +85,7 @@
// Set "Hello, World", and place the cursor at the beginning of the text.
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(0, 0))
+ selection = TextRange(0))
assertEquals("", ic.getTextBeforeCursor(5, 0))
assertEquals("Hello", ic.getTextAfterCursor(5, 0))
@@ -93,7 +93,7 @@
// Set "Hello, World", and place the cursor between "H" and "e".
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(1, 1))
+ selection = TextRange(1))
assertEquals("H", ic.getTextBeforeCursor(5, 0))
assertEquals("ello,", ic.getTextAfterCursor(5, 0))
@@ -101,7 +101,7 @@
// Set "Hello, World", and place the cursor at the end of the text.
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(12, 12))
+ selection = TextRange(12))
assertEquals("World", ic.getTextBeforeCursor(5, 0))
assertEquals("", ic.getTextAfterCursor(5, 0))
@@ -112,7 +112,7 @@
// Set "Hello, World", and place the cursor at the beginning of the text.
ic.mTextFieldValue = TextFieldValue(
text = "Hello, World",
- selection = TextRange(0, 0))
+ selection = TextRange(0))
assertEquals("", ic.getSelectedText(0))
@@ -135,7 +135,7 @@
fun commitTextTest() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
// Inserting "Hello, " into the empty text field.
assertTrue(ic.commitText("Hello, ", 1))
@@ -150,7 +150,7 @@
fun commitTextTest_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
// IME set text "Hello, World." with two commitText API within the single batch session.
// Do not callback to listener during batch session.
@@ -175,7 +175,7 @@
fun setComposingRegion() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
// Mark first "H" as composition.
assertTrue(ic.setComposingRegion(0, 1))
@@ -190,7 +190,7 @@
fun setComposingRegion_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
@@ -214,7 +214,7 @@
fun setComposingTextTest() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
// Inserting "Hello, " into the empty text field.
assertTrue(ic.setComposingText("Hello, ", 1))
@@ -229,7 +229,7 @@
fun setComposingTextTest_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
// IME set text "Hello, World." with two setComposingText API within the single batch
// session. Do not callback to listener during batch session.
@@ -254,7 +254,7 @@
fun deleteSurroundingText() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
// Delete first "Hello, " characters
assertTrue(ic.deleteSurroundingText(0, 6))
@@ -269,7 +269,7 @@
fun deleteSurroundingText_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
@@ -293,7 +293,7 @@
fun deleteSurroundingTextInCodePoints() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
// Delete first "Hello, " characters
assertTrue(ic.deleteSurroundingTextInCodePoints(0, 6))
@@ -308,7 +308,7 @@
fun deleteSurroundingTextInCodePoints_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
@@ -332,7 +332,7 @@
fun setSelection() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
// Select "Hello, "
assertTrue(ic.setSelection(0, 6))
@@ -347,7 +347,7 @@
fun setSelection_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
@@ -371,7 +371,7 @@
fun finishComposingText() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
// Cancel any ongoing composition. In this example, there is no composition range, but
// should record the API call
@@ -387,7 +387,7 @@
fun finishComposingText_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
@@ -411,7 +411,7 @@
fun mixedAPICalls_batchSession() {
val captor = argumentCaptor<List<EditOperation>>()
- ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+ ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
// Do not callback to listener during batch session.
ic.beginBatchEdit()
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
index b6b38a3..6d650d4 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
@@ -24,6 +24,7 @@
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue
import androidx.annotation.RequiresApi
+import androidx.ui.core.toAndroidRect
/**
* Autofill implementation for Android.
@@ -45,7 +46,8 @@
autofillManager.notifyViewEntered(
view,
autofillNode.id,
- autofillNode.boundingBox ?: error("requestAutofill called before onChildPositioned()")
+ autofillNode.boundingBox?.toAndroidRect()
+ ?: error("requestAutofill called before onChildPositioned()")
)
}
@@ -79,11 +81,12 @@
// null, the autofill overlay will not be shown.
Log.w(
"Autofill Warning",
- """Bounding box not set.
+ """Bounding box not set.
Did you call perform autofillTree before the component was positioned? """
)
}
- autofillNode.boundingBox?.run { setDimens(left, top, 0, 0, width(), height()) }
+ autofillNode.boundingBox?.toAndroidRect()?.run {
+ setDimens(left, top, 0, 0, width(), height()) }
}
index++
}
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
index cb8003e..059d6ee 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
@@ -40,7 +40,7 @@
private var onEditCommand: (List<EditOperation>) -> Unit = {}
private var onImeActionPerformed: (ImeAction) -> Unit = {}
- private var state = TextFieldValue(text = "", selection = TextRange(0, 0))
+ private var state = TextFieldValue(text = "", selection = TextRange(0))
private var keyboardType = KeyboardType.Text
private var imeAction = ImeAction.Unspecified
private var ic: RecordingInputConnection? = null
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
similarity index 94%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
index c11fd61..cf57171 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
@@ -16,8 +16,8 @@
package androidx.ui.autofill
-import android.graphics.Rect
-import androidx.annotation.GuardedBy
+import androidx.ui.geometry.Rect
+import androidx.ui.util.annotation.GuardedBy
/**
* Autofill API.
@@ -75,8 +75,8 @@
@GuardedBy("this")
private var previousId = 0
- @Synchronized
- private fun generateId() = ++previousId
+ private fun generateId() =
+ synchronized(this) { ++previousId }
}
val id: Int = generateId()
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillTree.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillTree.kt
similarity index 100%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillTree.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillTree.kt
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillType.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillType.kt
similarity index 100%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillType.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillType.kt
diff --git a/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt b/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
index 0081b3f..77550bc8 100644
--- a/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
+++ b/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
@@ -48,7 +48,7 @@
ic = RecordingInputConnection(
TextFieldValue(
"",
- TextRange(0, 0)
+ TextRange(0)
), listener
)
}
@@ -58,7 +58,7 @@
val imm: InputMethodManager = mock()
val view: View = mock()
- val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
ic.updateInputState(inputState, imm, view)
@@ -73,7 +73,7 @@
ic.getExtractedText(null, InputConnection.GET_EXTRACTED_TEXT_MONITOR)
- val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+ val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
ic.updateInputState(inputState, imm, view)
diff --git a/ui/ui-desktop/README.md b/ui/ui-desktop/README.md
index c266f16..f829147 100644
--- a/ui/ui-desktop/README.md
+++ b/ui/ui-desktop/README.md
@@ -2,4 +2,4 @@
To try:
- ./gradlew -Pcompose.desktop=1 :ui:ui-desktop:samples:run
\ No newline at end of file
+ ./gradlew :ui:ui-desktop:samples:run
diff --git a/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt b/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
index cf5b361..42216bb 100644
--- a/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
+++ b/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
@@ -35,9 +35,8 @@
ry: Float,
dir: Direction
) {
- println("Path.addRoundRect")
- // TODO: incorrect.
- skijaPath.addPoly(floatArrayOf(left, top, left, bottom, right, bottom, right, top), true)
+ skijaPath.addRoundedRect(org.jetbrains.skija.RoundedRect.makeLTRB(
+ left, top, right, bottom, rx, ry), dir.skija)
}
fun reset() {
@@ -49,20 +48,9 @@
radii: FloatArray,
dir: Direction
) {
- println("Path.addRoundRect")
- // TODO: incorrect.
- skijaPath.addPoly(floatArrayOf(
- rect.left,
- rect.top,
- rect.left,
- rect.bottom,
- rect.right,
- rect.bottom,
- rect.right,
- rect.top
- ), true)
+ skijaPath.addRoundedRect(org.jetbrains.skija.RoundedRect.makeComplexLTRB(
+ rect.left, rect.top, rect.right, rect.bottom, radii), dir.skija)
}
- // TODO: incorrect
- fun isConvex(): Boolean = true
+ fun isConvex(): Boolean = skijaPath.isConvex
}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
index bb904a6..dbb0db9 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
@@ -78,7 +78,7 @@
val state = state {
androidx.ui.input.TextFieldValue(
"abcde",
- TextRange(0, 0)
+ TextRange(0)
)
}
TextField(
@@ -128,7 +128,7 @@
.invoke(eq(
androidx.ui.input.TextFieldValue(
"ABCDEabcde",
- TextRange(5, 5)
+ TextRange(5)
)
))
}
@@ -152,7 +152,7 @@
.invoke(eq(
androidx.ui.input.TextFieldValue(
text = "ABCDEabcde",
- selection = TextRange(5, 5),
+ selection = TextRange(5),
composition = TextRange(0, composingText.length)
)
))
@@ -167,7 +167,7 @@
verify(onValueChange, times(1)).invoke(eq(
androidx.ui.input.TextFieldValue(
"abcde",
- TextRange(1, 1)
+ TextRange(1)
)
))
}
@@ -182,7 +182,7 @@
.invoke(eq(
androidx.ui.input.TextFieldValue(
text = "ABCDEabcde",
- selection = TextRange(5, 5),
+ selection = TextRange(5),
composition = TextRange(0, composingText.length)
)
))
@@ -201,7 +201,7 @@
verify(onValueChange, times(1)).invoke(eq(
androidx.ui.input.TextFieldValue(
"bcde",
- TextRange(0, 0)
+ TextRange(0)
)
))
}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
index 72bf9a5..0d93d9d 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
@@ -85,7 +85,7 @@
@Stable
val text: String = "",
@Stable
- val selection: TextRange = TextRange(0, 0)
+ val selection: TextRange = TextRange(0)
) {
companion object {
/**
diff --git a/ui/ui-graphics/api/0.1.0-dev14.txt b/ui/ui-graphics/api/0.1.0-dev14.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/0.1.0-dev14.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/api/api_lint.ignore b/ui/ui-graphics/api/api_lint.ignore
index e82ea03..2733c6a 100644
--- a/ui/ui-graphics/api/api_lint.ignore
+++ b/ui/ui-graphics/api/api_lint.ignore
@@ -101,5 +101,13 @@
Missing nullability on parameter `p` in method `toString-impl`
+NotCloseable: androidx.ui.graphics.AndroidPath:
+ Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.AndroidPath
+NotCloseable: androidx.ui.graphics.Path:
+ Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.Path
+NotCloseable: androidx.ui.graphics.vector.PathBuilder:
+ Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.vector.PathBuilder
+
+
TopLevelBuilder: androidx.ui.graphics.vector.PathBuilder:
Builder should be defined as inner class: androidx.ui.graphics.vector.PathBuilder
diff --git a/ui/ui-graphics/api/current.txt b/ui/ui-graphics/api/current.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/current.txt
+++ b/ui/ui-graphics/api/current.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/api/public_plus_experimental_current.txt b/ui/ui-graphics/api/public_plus_experimental_current.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/api/restricted_0.1.0-dev14.txt b/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
index cc358bd..7c2c958 100644
--- a/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/api/restricted_current.txt b/ui/ui-graphics/api/restricted_current.txt
index cc358bd..7c2c958 100644
--- a/ui/ui-graphics/api/restricted_current.txt
+++ b/ui/ui-graphics/api/restricted_current.txt
@@ -4,6 +4,7 @@
public final class RectHelperKt {
method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+ method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
}
}
diff --git a/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt b/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
index 06d24c1..7517871 100644
--- a/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
+++ b/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
@@ -42,3 +42,15 @@
bottom
)
}
+
+/**
+ * Creates a new instance of [androidx.ui.geometry.Rect] with the same bounds
+ * specified in the given [android.graphics.Rect]
+ */
+fun android.graphics.Rect.toComposeRect(): androidx.ui.geometry.Rect =
+ androidx.ui.geometry.Rect(
+ this.left.toFloat(),
+ this.top.toFloat(),
+ this.right.toFloat(),
+ this.bottom.toFloat()
+ )
\ No newline at end of file
diff --git a/ui/ui-material/api/0.1.0-dev14.txt b/ui/ui-material/api/0.1.0-dev14.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/0.1.0-dev14.txt
+++ b/ui/ui-material/api/0.1.0-dev14.txt
@@ -278,6 +278,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/api_lint.ignore b/ui/ui-material/api/api_lint.ignore
new file mode 100644
index 0000000..7e1573a
--- /dev/null
+++ b/ui/ui-material/api/api_lint.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+NotCloseable: androidx.ui.material.ripple.RippleEffect:
+ Classes that release resources (finish()) should implement AutoClosable and CloseGuard: class androidx.ui.material.ripple.RippleEffect
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -278,6 +278,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
@@ -278,6 +278,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -278,6 +278,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/restricted_0.1.0-dev14.txt b/ui/ui-material/api/restricted_0.1.0-dev14.txt
index 7bb3d57..200bcbe 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev14.txt
@@ -279,6 +279,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 7bb3d57..200bcbe 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -279,6 +279,8 @@
method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
@androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
index 2aaee29..f7c1dbb 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
@@ -38,7 +38,7 @@
ComposableDemo("Emphasis") { EmphasisSample() },
ComposableDemo("ListItems") { ListItemDemo() },
DemoCategory("TextFields", listOf(
- ComposableDemo("FilledTextField") { FilledTextFieldDemo() },
+ ComposableDemo("FilledTextField/OutlinedTextField") { MaterialTextFieldDemo() },
ComposableDemo("Multiple text fields") { TextFieldsDemo() }
)),
ComposableDemo("Material Theme") { MaterialThemeDemo() },
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
index dbbca87..1c4d61d 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
@@ -20,14 +20,12 @@
import androidx.compose.getValue
import androidx.compose.setValue
import androidx.ui.core.Alignment
-import androidx.ui.core.DensityAmbient
-import androidx.ui.core.LayoutDirection
import androidx.ui.core.Modifier
import androidx.ui.foundation.Box
import androidx.ui.foundation.Icon
import androidx.ui.foundation.Text
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.graphics.Color
-import androidx.ui.layout.Arrangement
import androidx.ui.layout.Column
import androidx.ui.layout.ColumnScope.gravity
import androidx.ui.layout.Row
@@ -40,6 +38,7 @@
import androidx.ui.material.EmphasisAmbient
import androidx.ui.material.FilledTextField
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.OutlinedTextField
import androidx.ui.material.RadioGroup
import androidx.ui.material.icons.Icons
import androidx.ui.material.icons.filled.Favorite
@@ -49,55 +48,75 @@
import androidx.ui.material.samples.FilledTextFieldWithIcons
import androidx.ui.material.samples.FilledTextFieldWithPlaceholder
import androidx.ui.material.samples.PasswordFilledTextField
+import androidx.ui.material.samples.SimpleOutlinedTextFieldSample
import androidx.ui.material.samples.TextFieldWithHelperMessage
import androidx.ui.material.samples.TextFieldWithHideKeyboardOnImeAction
import androidx.ui.savedinstancestate.savedInstanceState
import androidx.ui.unit.dp
+
@Composable
fun TextFieldsDemo() {
- val space = with(DensityAmbient.current) { 5.dp.toIntPx() }
- Column(
- modifier = Modifier.fillMaxHeight().padding(10.dp),
- verticalArrangement = arrangeWithSpacer(space)
- ) {
- Text("Password text field")
- PasswordFilledTextField()
- Text("Text field with leading and trailing icons")
- FilledTextFieldWithIcons()
- Text("Text field with placeholder")
- FilledTextFieldWithPlaceholder()
- Text("Text field with error state handling")
- FilledTextFieldWithErrorState()
- Text("Text field with helper/error message")
- TextFieldWithHelperMessage()
- Text("Hide keyboard on IME action")
- TextFieldWithHideKeyboardOnImeAction()
- Text("TextFieldValue overload")
- FilledTextFieldSample()
+ VerticalScroller {
+ Column(
+ modifier = Modifier.fillMaxHeight().padding(10.dp)
+ ) {
+ Text("Password text field")
+ PasswordFilledTextField()
+ Text("Text field with leading and trailing icons")
+ FilledTextFieldWithIcons()
+ Text("Outlined text field")
+ SimpleOutlinedTextFieldSample()
+ Text("Text field with placeholder")
+ FilledTextFieldWithPlaceholder()
+ Text("Text field with error state handling")
+ FilledTextFieldWithErrorState()
+ Text("Text field with helper/error message")
+ TextFieldWithHelperMessage()
+ Text("Hide keyboard on IME action")
+ TextFieldWithHideKeyboardOnImeAction()
+ Text("TextFieldValue overload")
+ FilledTextFieldSample()
+ }
}
}
@Composable
-fun FilledTextFieldDemo() {
+fun MaterialTextFieldDemo() {
Column(Modifier.padding(10.dp)) {
var text by savedInstanceState { "" }
var leadingChecked by savedInstanceState { false }
var trailingChecked by savedInstanceState { false }
val characterCounterChecked by savedInstanceState { false }
var selectedOption by savedInstanceState { Option.None }
+ var selectedTextField by savedInstanceState { TextFieldType.Filled }
- val textField = @Composable {
- FilledTextField(
- value = text,
- onValueChange = { text = it },
- label = {
- val label = "Label" + if (selectedOption == Option.Error) "*" else ""
- Text(text = label)
- },
- leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
- trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
- isErrorValue = selectedOption == Option.Error
- )
+ val textField: @Composable () -> Unit = @Composable {
+ when (selectedTextField) {
+ TextFieldType.Filled ->
+ FilledTextField(
+ value = text,
+ onValueChange = { text = it },
+ label = {
+ val label = "Label" + if (selectedOption == Option.Error) "*" else ""
+ Text(text = label)
+ },
+ leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+ trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+ isErrorValue = selectedOption == Option.Error
+ )
+ TextFieldType.Outlined ->
+ OutlinedTextField(
+ value = text,
+ onValueChange = { text = it },
+ label = {
+ val label = "Label" + if (selectedOption == Option.Error) "*" else ""
+ Text(text = label)
+ },
+ leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+ trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+ isErrorValue = selectedOption == Option.Error
+ )
+ }
}
Box(Modifier.preferredHeight(150.dp).gravity(Alignment.CenterHorizontally)) {
@@ -109,6 +128,13 @@
}
Column {
+ Title("Text field type")
+ RadioGroup(
+ options = TextFieldType.values().map { it.name },
+ selectedOption = selectedTextField.name,
+ onSelectedChange = { selectedTextField = TextFieldType.valueOf(it) }
+ )
+
Title("Options")
OptionRow(
title = "Leading icon",
@@ -193,18 +219,4 @@
*/
private enum class Option { None, Helper, Error }
-private fun arrangeWithSpacer(space: Int) = object : Arrangement.Vertical {
- override fun arrange(
- totalSize: Int,
- size: List<Int>,
- layoutDirection: LayoutDirection
- ): List<Int> {
- val positions = mutableListOf<Int>()
- var current = 0
- size.forEach {
- positions.add(current)
- current += (it + space)
- }
- return positions
- }
-}
\ No newline at end of file
+private enum class TextFieldType { Filled, Outlined }
diff --git a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
index a472fef..c3f4da4 100644
--- a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
+++ b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
@@ -32,6 +32,7 @@
import androidx.ui.material.EmphasisAmbient
import androidx.ui.material.FilledTextField
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.OutlinedTextField
import androidx.ui.material.icons.Icons
import androidx.ui.material.icons.filled.Favorite
import androidx.ui.material.icons.filled.Info
@@ -53,6 +54,18 @@
@Sampled
@Composable
+fun SimpleOutlinedTextFieldSample() {
+ var text by savedInstanceState { "" }
+
+ OutlinedTextField(
+ value = text,
+ onValueChange = { text = it },
+ label = { Text("Label") }
+ )
+}
+
+@Sampled
+@Composable
fun FilledTextFieldWithIcons() {
var text by savedInstanceState { "" }
@@ -153,6 +166,20 @@
@Sampled
@Composable
+fun OutlinedTextFieldSample() {
+ var text by savedInstanceState(saver = TextFieldValue.Saver) {
+ TextFieldValue("example", TextRange(0, 7))
+ }
+
+ OutlinedTextField(
+ value = text,
+ onValueChange = { text = it },
+ label = { Text("Label") }
+ )
+}
+
+@Sampled
+@Composable
fun TextFieldWithHideKeyboardOnImeAction() {
var text by savedInstanceState { "" }
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt
deleted file mode 100644
index b9256b4..0000000
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt
+++ /dev/null
@@ -1,961 +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.ui.material
-
-import android.os.Build
-import androidx.compose.Providers
-import androidx.compose.remember
-import androidx.compose.state
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import androidx.ui.core.Modifier
-import androidx.ui.core.Ref
-import androidx.ui.core.TextInputServiceAmbient
-import androidx.ui.core.globalPosition
-import androidx.ui.core.onPositioned
-import androidx.ui.core.positionInRoot
-import androidx.ui.core.testTag
-import androidx.ui.foundation.Box
-import androidx.ui.foundation.Text
-import androidx.ui.foundation.TextField
-import androidx.ui.foundation.contentColor
-import androidx.ui.foundation.currentTextStyle
-import androidx.ui.foundation.drawBackground
-import androidx.ui.geometry.Offset
-import androidx.ui.graphics.Color
-import androidx.ui.graphics.RectangleShape
-import androidx.ui.graphics.compositeOver
-import androidx.ui.input.ImeAction
-import androidx.ui.input.KeyboardType
-import androidx.ui.input.PasswordVisualTransformation
-import androidx.ui.input.TextFieldValue
-import androidx.ui.input.TextInputService
-import androidx.ui.layout.Column
-import androidx.ui.layout.Stack
-import androidx.ui.layout.fillMaxWidth
-import androidx.ui.layout.preferredHeight
-import androidx.ui.layout.preferredSize
-import androidx.ui.savedinstancestate.rememberSavedInstanceState
-import androidx.ui.test.StateRestorationTester
-import androidx.ui.test.assertPixels
-import androidx.ui.test.assertShape
-import androidx.ui.test.captureToBitmap
-import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
-import androidx.ui.test.doGesture
-import androidx.ui.test.doSendImeAction
-import androidx.ui.test.findByTag
-import androidx.ui.test.runOnIdleCompose
-import androidx.ui.test.sendClick
-import androidx.ui.test.sendSwipeDown
-import androidx.ui.test.sendSwipeUp
-import androidx.ui.test.waitForIdle
-import androidx.ui.text.FirstBaseline
-import androidx.ui.text.SoftwareKeyboardController
-import androidx.ui.unit.IntSize
-import androidx.ui.unit.dp
-import androidx.ui.unit.sp
-import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.atLeastOnce
-import com.nhaarman.mockitokotlin2.eq
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.verify
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import kotlin.math.roundToInt
-
-@MediumTest
-@RunWith(JUnit4::class)
-class FilledTextFieldTest {
-
- private val ExpectedMinimumTextFieldHeight = 56.dp
- private val ExpectedPadding = 16.dp
- private val IconPadding = 12.dp
- private val ExpectedBaselineOffset = 20.dp
- private val ExpectedLastBaselineOffset = 16.dp
- private val IconColorAlpha = 0.54f
-
- private val LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
- "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
- " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
- "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
- "fugiat nulla pariatur."
-
- @get:Rule
- val testRule = createComposeRule()
-
- @Test
- fun testTextFieldMinimumHeight() {
- testRule
- .setMaterialContentAndCollectSizes {
- FilledTextField(
- value = "input",
- onValueChange = {},
- label = {},
- modifier = Modifier.preferredHeight(20.dp)
- )
- }
- .assertHeightEqualsTo(ExpectedMinimumTextFieldHeight)
- }
-
- @Test
- fun testTextField_singleFocus() {
- var textField1Focused = false
- val textField1Tag = "TextField1"
-
- var textField2Focused = false
- val textField2Tag = "TextField2"
-
- testRule.setMaterialContent {
- Column {
- FilledTextField(
- modifier = Modifier.testTag(textField1Tag),
- value = "input1",
- onValueChange = {},
- label = {},
- onFocusChange = { textField1Focused = it }
- )
- FilledTextField(
- modifier = Modifier.testTag(textField2Tag),
- value = "input2",
- onValueChange = {},
- label = {},
- onFocusChange = { textField2Focused = it }
- )
- }
- }
-
- findByTag(textField1Tag).doClick()
-
- runOnIdleCompose {
- assertThat(textField1Focused).isTrue()
- assertThat(textField2Focused).isFalse()
- }
-
- findByTag(textField2Tag).doClick()
-
- runOnIdleCompose {
- assertThat(textField1Focused).isFalse()
- assertThat(textField2Focused).isTrue()
- }
- }
-
- @Test
- fun testGetFocus_whenClickedOnSurfaceArea() {
- var focused = false
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "input",
- onValueChange = {},
- label = {},
- onFocusChange = { focused = it }
- )
- }
- }
-
- // Click on (2, 2) which is Surface area and outside input area
- findByTag("textField").doGesture {
- sendClick(Offset(2f, 2f))
- }
-
- testRule.runOnIdleComposeWithDensity {
- assertThat(focused).isTrue()
- }
- }
-
- @Test
- fun testLabelPosition_initial_withDefaultHeight() {
- val labelSize = Ref<IntSize>()
- val labelPosition = Ref<Offset>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- value = "",
- onValueChange = {},
- label = {
- Text(
- text = "label",
- fontSize = 10.sp,
- modifier = Modifier
- .onPositioned {
- labelPosition.value = it.globalPosition
- labelSize.value = it.size
- }
- )
- },
- modifier = Modifier.preferredHeight(56.dp)
- )
- }
- }
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(labelSize.value).isNotNull()
- assertThat(labelSize.value?.height).isGreaterThan(0)
- assertThat(labelSize.value?.width).isGreaterThan(0)
- // centered position
- assertThat(labelPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(labelPosition.value?.y).isEqualTo(
- ((ExpectedMinimumTextFieldHeight.toIntPx() - labelSize.value!!.height) / 2f)
- .roundToInt().toFloat()
- )
- }
- }
-
- @Test
- fun testLabelPosition_initial_withCustomHeight() {
- val height = 80.dp
- val labelSize = Ref<IntSize>()
- val labelPosition = Ref<Offset>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- value = "",
- onValueChange = {},
- modifier = Modifier.preferredHeight(height),
- label = {
- Text(text = "label", modifier = Modifier.onPositioned {
- labelPosition.value = it.globalPosition
- labelSize.value = it.size
- })
- }
- )
- }
- }
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(labelSize.value).isNotNull()
- assertThat(labelSize.value?.height).isGreaterThan(0)
- assertThat(labelSize.value?.width).isGreaterThan(0)
- // centered position
- assertThat(labelPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(labelPosition.value?.y).isEqualTo(
- ((height.toIntPx() - labelSize.value!!.height) / 2f).roundToInt().toFloat()
- )
- }
- }
-
- @Test
- fun testLabelPosition_whenFocused() {
- val labelSize = Ref<IntSize>()
- val labelPosition = Ref<Offset>()
- val baseline = Ref<Float>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "",
- onValueChange = {},
- label = {
- Text(text = "label", modifier = Modifier.onPositioned {
- labelPosition.value = it.globalPosition
- labelSize.value = it.size
- baseline.value =
- it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
- })
- }
- )
- }
- }
-
- // click to focus
- clickAndAdvanceClock("textField", 200)
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(labelSize.value).isNotNull()
- assertThat(labelSize.value?.height).isGreaterThan(0)
- assertThat(labelSize.value?.width).isGreaterThan(0)
- // label's top position
- assertThat(labelPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(baseline.value).isEqualTo(
- ExpectedBaselineOffset.toIntPx().toFloat()
- )
- }
- }
-
- @Test
- fun testLabelPosition_whenInput() {
- val labelSize = Ref<IntSize>()
- val labelPosition = Ref<Offset>()
- val baseline = Ref<Float>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- value = "input",
- onValueChange = {},
- label = {
- Text(text = "label", modifier = Modifier.onPositioned {
- labelPosition.value = it.globalPosition
- labelSize.value = it.size
- baseline.value =
- it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
- })
- }
- )
- }
- }
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(labelSize.value).isNotNull()
- assertThat(labelSize.value?.height).isGreaterThan(0)
- assertThat(labelSize.value?.width).isGreaterThan(0)
- // label's top position
- assertThat(labelPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(baseline.value).isEqualTo(
- ExpectedBaselineOffset.toIntPx().toFloat()
- )
- }
- }
-
- @Test
- fun testPlaceholderPosition_withLabel() {
- val placeholderSize = Ref<IntSize>()
- val placeholderPosition = Ref<Offset>()
- val placeholderBaseline = Ref<Float>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- modifier = Modifier
- .preferredHeight(60.dp)
- .testTag("textField"),
- value = "",
- onValueChange = {},
- label = { Text("label") },
- placeholder = {
- Text(text = "placeholder", modifier = Modifier.onPositioned {
- placeholderPosition.value = it.globalPosition
- placeholderSize.value = it.size
- placeholderBaseline.value =
- it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
- })
- }
- )
- }
- }
- // click to focus
- clickAndAdvanceClock("textField", 200)
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(placeholderSize.value).isNotNull()
- assertThat(placeholderSize.value?.height).isGreaterThan(0)
- assertThat(placeholderSize.value?.width).isGreaterThan(0)
- // placeholder's position
- assertThat(placeholderPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(placeholderBaseline.value)
- .isEqualTo(
- 60.dp.toIntPx().toFloat() -
- ExpectedLastBaselineOffset.toIntPx().toFloat()
- )
- }
- }
-
- @Test
- fun testPlaceholderPosition_whenNoLabel() {
- val placeholderSize = Ref<IntSize>()
- val placeholderPosition = Ref<Offset>()
- val placeholderBaseline = Ref<Float>()
- val height = 60.dp
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- modifier = Modifier.preferredHeight(height).testTag("textField"),
- value = "",
- onValueChange = {},
- label = {},
- placeholder = {
- Text(text = "placeholder", modifier = Modifier.onPositioned {
- placeholderPosition.value = it.positionInRoot
- placeholderSize.value = it.size
- placeholderBaseline.value =
- it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
- })
- }
- )
- }
- }
- // click to focus
- clickAndAdvanceClock("textField", 200)
-
- testRule.runOnIdleComposeWithDensity {
- // size
- assertThat(placeholderSize.value).isNotNull()
- assertThat(placeholderSize.value?.height).isGreaterThan(0)
- assertThat(placeholderSize.value?.width).isGreaterThan(0)
- // centered position
- assertThat(placeholderPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- assertThat(placeholderPosition.value?.y).isEqualTo(
- ((height.toIntPx() - placeholderSize.value!!.height) / 2f).roundToInt().toFloat()
- )
- }
- }
-
- @Test
- fun testNoPlaceholder_whenInputNotEmpty() {
- val placeholderSize = Ref<IntSize>()
- val placeholderPosition = Ref<Offset>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "input",
- onValueChange = {},
- label = {},
- placeholder = {
- Text(text = "placeholder", modifier = Modifier.onPositioned {
- placeholderPosition.value = it.globalPosition
- placeholderSize.value = it.size
- })
- }
- )
- }
- }
-
- // click to focus
- clickAndAdvanceClock("textField", 200)
-
- testRule.runOnIdleComposeWithDensity {
- assertThat(placeholderSize.value).isNull()
- assertThat(placeholderPosition.value).isNull()
- }
- }
-
- @Test
- fun testPlaceholderColorAndTextStyle() {
- testRule.setMaterialContent {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "",
- onValueChange = {},
- label = {},
- placeholder = {
- Text("placeholder")
- assertThat(contentColor())
- .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
- assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
- }
- )
- }
-
- // click to focus
- findByTag("textField").doClick()
- }
-
- @Test
- fun testTrailingAndLeading_sizeAndPosition() {
- val textFieldHeight = 60.dp
- val textFieldWidth = 300.dp
- val size = 30.dp
- val leadingPosition = Ref<Offset>()
- val leadingSize = Ref<IntSize>()
- val trailingPosition = Ref<Offset>()
- val trailingSize = Ref<IntSize>()
-
- testRule.setMaterialContent {
- FilledTextField(
- value = "text",
- onValueChange = {},
- modifier = Modifier.preferredSize(textFieldWidth, textFieldHeight),
- label = {},
- leadingIcon = {
- Box(Modifier.preferredSize(size).onPositioned {
- leadingPosition.value = it.globalPosition
- leadingSize.value = it.size
- })
- },
- trailingIcon = {
- Box(Modifier.preferredSize(size).onPositioned {
- trailingPosition.value = it.globalPosition
- trailingSize.value = it.size
- })
- }
- )
- }
-
- testRule.runOnIdleComposeWithDensity {
- // leading
- assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
- assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
- assertThat(leadingPosition.value?.y).isEqualTo(
- ((textFieldHeight.toIntPx() - leadingSize.value!!.height) / 2f).roundToInt()
- .toFloat()
- )
- // trailing
- assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
- assertThat(trailingPosition.value?.x).isEqualTo(
- (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
- .toFloat()
- )
- assertThat(trailingPosition.value?.y)
- .isEqualTo(((textFieldHeight.toIntPx() - trailingSize.value!!.height) / 2f)
- .roundToInt().toFloat()
- )
- }
- }
-
- @Test
- fun testLabelPositionX_initial_withTrailingAndLeading() {
- val height = 60.dp
- val iconSize = 30.dp
- val labelPosition = Ref<Offset>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- value = "",
- onValueChange = {},
- modifier = Modifier.preferredHeight(height),
- label = {
- Text(text = "label", modifier = Modifier.onPositioned {
- labelPosition.value = it.globalPosition
- })
- },
- trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
- leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
- )
- }
- }
-
- testRule.runOnIdleComposeWithDensity {
- assertThat(labelPosition.value?.x).isEqualTo(
- (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
- .toFloat()
- )
- }
- }
-
- @Test
- fun testLabelPositionX_initial_withEmptyTrailingAndLeading() {
- val height = 60.dp
- val labelPosition = Ref<Offset>()
- testRule.setMaterialContent {
- Box {
- FilledTextField(
- value = "",
- onValueChange = {},
- modifier = Modifier.preferredHeight(height),
- label = {
- Text(text = "label", modifier = Modifier.onPositioned {
- labelPosition.value = it.globalPosition
- })
- },
- trailingIcon = {},
- leadingIcon = {}
- )
- }
- }
-
- testRule.runOnIdleComposeWithDensity {
- assertThat(labelPosition.value?.x).isEqualTo(
- ExpectedPadding.toIntPx().toFloat()
- )
- }
- }
-
- @Test
- fun testColorInLeadingTrailing_whenValidInput() {
- testRule.setMaterialContent {
- FilledTextField(
- value = "",
- onValueChange = {},
- label = {},
- isErrorValue = false,
- leadingIcon = {
- assertThat(contentColor())
- .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
- },
- trailingIcon = {
- assertThat(contentColor())
- .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
- }
- )
- }
- }
-
- @Test
- fun testColorInLeadingTrailing_whenInvalidInput() {
- testRule.setMaterialContent {
- FilledTextField(
- value = "",
- onValueChange = {},
- label = {},
- isErrorValue = true,
- leadingIcon = {
- assertThat(contentColor())
- .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
- },
- trailingIcon = {
- assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
- }
- )
- }
- }
-
- @Test
- fun testImeActionAndKeyboardTypePropagatedDownstream() {
- val textInputService = mock<TextInputService>()
- testRule.setContent {
- Providers(
- TextInputServiceAmbient provides textInputService
- ) {
- var text = state { TextFieldValue("") }
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = text.value,
- onValueChange = { text.value = it },
- label = {},
- imeAction = ImeAction.Go,
- keyboardType = KeyboardType.Email
- )
- }
- }
-
- clickAndAdvanceClock("textField", 200)
-
- runOnIdleCompose {
- verify(textInputService, atLeastOnce()).startInput(
- value = any(),
- keyboardType = eq(KeyboardType.Email),
- imeAction = eq(ImeAction.Go),
- onEditCommand = any(),
- onImeActionPerformed = any()
- )
- }
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun testVisualTransformationPropagated() {
- testRule.setMaterialContent {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "qwerty",
- onValueChange = {},
- label = {},
- visualTransformation = PasswordVisualTransformation('\u0020'),
- backgroundColor = Color.White,
- shape = RectangleShape
- )
- }
-
- findByTag("textField")
- .captureToBitmap()
- .assertShape(
- density = testRule.density,
- backgroundColor = Color.White,
- shapeColor = Color.White,
- shape = RectangleShape,
- // avoid elevation artifacts
- shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
- )
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun test_alphaNotSet_toBackgroundColorAndTransparentColors() {
- val latch = CountDownLatch(1)
-
- testRule.setMaterialContent {
- Stack(Modifier.drawBackground(Color.White)) {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "",
- onValueChange = {},
- label = {},
- shape = RectangleShape,
- backgroundColor = Color.Blue,
- activeColor = Color.Transparent,
- inactiveColor = Color.Transparent,
- onFocusChange = { focused ->
- if (focused) latch.countDown()
- }
- )
- }
- }
-
- val expectedColor = Color.Blue.copy(alpha = 0.12f).compositeOver(Color.White)
-
- findByTag("textField")
- .captureToBitmap()
- .assertShape(
- density = testRule.density,
- backgroundColor = Color.White,
- shapeColor = expectedColor,
- shape = RectangleShape,
- // avoid elevation artifacts
- shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
- )
-
- findByTag("textField").doClick()
- assert(latch.await(1, TimeUnit.SECONDS))
-
- findByTag("textField")
- .captureToBitmap()
- .assertShape(
- density = testRule.density,
- backgroundColor = Color.White,
- shapeColor = expectedColor,
- shape = RectangleShape,
- // avoid elevation artifacts
- shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
- )
- }
-
- @Test
- fun testOnTextInputStartedCallback() {
- var controller: SoftwareKeyboardController? = null
-
- testRule.setMaterialContent {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "",
- onValueChange = {},
- label = {},
- onTextInputStarted = {
- controller = it
- }
- )
- }
- assertThat(controller).isNull()
-
- findByTag("textField")
- .doClick()
-
- runOnIdleCompose {
- assertThat(controller).isNotNull()
- }
- }
-
- @Test
- fun testImeActionCallback_withSoftwareKeyboardController() {
- var controller: SoftwareKeyboardController? = null
-
- testRule.setMaterialContent {
- FilledTextField(
- modifier = Modifier.testTag("textField"),
- value = "",
- onValueChange = {},
- label = {},
- imeAction = ImeAction.Go,
- onImeActionPerformed = { _, softwareKeyboardController ->
- controller = softwareKeyboardController
- }
- )
- }
- assertThat(controller).isNull()
-
- findByTag("textField")
- .doSendImeAction()
-
- runOnIdleCompose {
- assertThat(controller).isNotNull()
- }
- }
-
- @Test
- fun textField_scrollable_withLongInput() {
- val scrollerPosition = TextFieldScrollerPosition()
- testRule.setContent {
- Stack {
- TextFieldScroller(
- remember { scrollerPosition },
- Modifier.fillMaxWidth().preferredHeight(40.dp)
- ) {
- TextField(
- value = TextFieldValue(LONG_TEXT),
- onValueChange = {}
- )
- }
- }
- }
-
- runOnIdleCompose {
- assertThat(scrollerPosition.maximum).isLessThan(Float.POSITIVE_INFINITY)
- assertThat(scrollerPosition.maximum).isGreaterThan(0f)
- }
- }
-
- @Test
- fun textField_notScrollable_withShortInput() {
- val text = "text"
- val scrollerPosition = TextFieldScrollerPosition()
- testRule.setContent {
- Stack {
- TextFieldScroller(
- remember { scrollerPosition },
- Modifier.fillMaxWidth().preferredHeight(100.dp)
- ) {
- TextField(
- value = TextFieldValue(text),
- onValueChange = {}
- )
- }
- }
- }
-
- runOnIdleCompose {
- assertThat(scrollerPosition.maximum).isEqualTo(0f)
- }
- }
-
- @Test
- fun textField_scrolledAndClipped() {
- val tag = "testTag"
- val scrollerPosition = TextFieldScrollerPosition()
-
- val parentSize = 200
- val textFieldSize = 50
-
- with(testRule.density) {
- testRule.setContent {
- Stack(
- Modifier
- .preferredSize(parentSize.toDp())
- .drawBackground(Color.White)
- .testTag(tag)
- ) {
- TextFieldScroller(
- remember { scrollerPosition },
- Modifier.preferredSize(textFieldSize.toDp())
- ) {
- TextField(
- value = TextFieldValue(LONG_TEXT),
- onValueChange = {}
- )
- }
- }
- }
- }
-
- runOnIdleCompose {}
-
- findByTag(tag)
- .captureToBitmap()
- .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
- if (position.x > textFieldSize && position.y > textFieldSize) Color.White else null
- }
- }
-
- @Test
- fun textField_swipe_whenLongInput() {
- val tag = "testTag"
- val scrollerPosition = TextFieldScrollerPosition()
-
- testRule.setContent {
- Stack {
- TextFieldScroller(
- remember { scrollerPosition },
- Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(tag)
- ) {
- TextField(
- value = TextFieldValue(LONG_TEXT),
- onValueChange = {}
- )
- }
- }
- }
-
- runOnIdleCompose {
- assertThat(scrollerPosition.current).isEqualTo(0f)
- }
-
- findByTag(tag)
- .doGesture { sendSwipeDown() }
-
- val firstSwipePosition = runOnIdleCompose {
- scrollerPosition.current
- }
- assertThat(firstSwipePosition).isGreaterThan(0f)
-
- findByTag(tag)
- .doGesture { sendSwipeUp() }
- runOnIdleCompose {
- assertThat(scrollerPosition.current).isLessThan(firstSwipePosition)
- }
- }
-
- @Test
- fun textFieldScroller_restoresScrollerPosition() {
- val tag = "scroller"
- val restorationTester = StateRestorationTester(testRule)
- var scrollerPosition = TextFieldScrollerPosition()
-
- restorationTester.setContent {
- scrollerPosition = rememberSavedInstanceState(
- saver = TextFieldScrollerPosition.Saver
- ) {
- TextFieldScrollerPosition()
- }
- TextFieldScroller(
- scrollerPosition,
- Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(tag)
- ) {
- TextField(
- value = TextFieldValue(LONG_TEXT),
- onValueChange = {}
- )
- }
- }
-
- findByTag(tag)
- .doGesture { sendSwipeDown() }
-
- val swipePosition = runOnIdleCompose {
- scrollerPosition.current
- }
- assertThat(swipePosition).isGreaterThan(0f)
-
- runOnIdleCompose {
- scrollerPosition = TextFieldScrollerPosition()
- assertThat(scrollerPosition.current).isEqualTo(0f)
- }
-
- restorationTester.emulateSavedInstanceStateRestore()
-
- runOnIdleCompose {
- assertThat(scrollerPosition.current).isEqualTo(swipePosition)
- }
- }
-
- private fun clickAndAdvanceClock(tag: String, time: Long) {
- findByTag(tag).doClick()
- waitForIdle()
- testRule.clockTestRule.pauseClock()
- testRule.clockTestRule.advanceClock(time)
- }
-}
\ No newline at end of file
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt
new file mode 100644
index 0000000..b56c46d
--- /dev/null
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt
@@ -0,0 +1,1488 @@
+/*
+ * 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.ui.material
+
+import android.os.Build
+import androidx.compose.Providers
+import androidx.compose.getValue
+import androidx.compose.remember
+import androidx.compose.setValue
+import androidx.compose.state
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.ui.core.Modifier
+import androidx.ui.core.Ref
+import androidx.ui.core.TextInputServiceAmbient
+import androidx.ui.core.globalPosition
+import androidx.ui.core.onPositioned
+import androidx.ui.core.positionInRoot
+import androidx.ui.core.testTag
+import androidx.ui.foundation.Box
+import androidx.ui.foundation.Text
+import androidx.ui.foundation.TextField
+import androidx.ui.foundation.contentColor
+import androidx.ui.foundation.currentTextStyle
+import androidx.ui.foundation.drawBackground
+import androidx.ui.geometry.Offset
+import androidx.ui.graphics.Color
+import androidx.ui.graphics.RectangleShape
+import androidx.ui.graphics.compositeOver
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.input.PasswordVisualTransformation
+import androidx.ui.input.TextFieldValue
+import androidx.ui.input.TextInputService
+import androidx.ui.layout.Column
+import androidx.ui.layout.Stack
+import androidx.ui.layout.fillMaxWidth
+import androidx.ui.layout.preferredHeight
+import androidx.ui.layout.preferredSize
+import androidx.ui.savedinstancestate.rememberSavedInstanceState
+import androidx.ui.test.StateRestorationTester
+import androidx.ui.test.assertPixels
+import androidx.ui.test.assertShape
+import androidx.ui.test.captureToBitmap
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
+import androidx.ui.test.doSendImeAction
+import androidx.ui.test.findByTag
+import androidx.ui.test.runOnIdleCompose
+import androidx.ui.test.sendClick
+import androidx.ui.test.sendSwipeDown
+import androidx.ui.test.sendSwipeUp
+import androidx.ui.test.waitForIdle
+import androidx.ui.text.FirstBaseline
+import androidx.ui.text.SoftwareKeyboardController
+import androidx.ui.unit.IntSize
+import androidx.ui.unit.dp
+import androidx.ui.unit.sp
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.atLeastOnce
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.roundToInt
+import androidx.ui.layout.preferredWidth
+
+@MediumTest
+@RunWith(JUnit4::class)
+class TextFieldTest {
+
+ private val ExpectedMinimumTextFieldHeight = 56.dp
+ private val ExpectedPadding = 16.dp
+ private val IconPadding = 12.dp
+ private val ExpectedBaselineOffset = 20.dp
+ private val ExpectedLastBaselineOffset = 16.dp
+ private val IconColorAlpha = 0.54f
+ private val TextfieldTag = "textField"
+
+ private val LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+ " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+ "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur."
+
+ @get:Rule
+ val testRule = createComposeRule()
+
+ @Test
+ fun testFilledTextField_minimumHeight() {
+ testRule
+ .setMaterialContentAndCollectSizes {
+ FilledTextField(
+ value = "input",
+ onValueChange = {},
+ label = {},
+ modifier = Modifier.preferredHeight(20.dp)
+ )
+ }
+ .assertHeightEqualsTo(ExpectedMinimumTextFieldHeight)
+ }
+
+ @Test
+ fun testTextFields_singleFocus() {
+ var textField1Focused = false
+ val textField1Tag = "TextField1"
+
+ var textField2Focused = false
+ val textField2Tag = "TextField2"
+
+ var textField3Focused = false
+ val textField3Tag = "TextField3"
+
+ testRule.setMaterialContent {
+ Column {
+ FilledTextField(
+ modifier = Modifier.testTag(textField1Tag),
+ value = "input1",
+ onValueChange = {},
+ label = {},
+ onFocusChange = { textField1Focused = it }
+ )
+ OutlinedTextField(
+ modifier = Modifier.testTag(textField2Tag),
+ value = "input2",
+ onValueChange = {},
+ label = {},
+ onFocusChange = { textField2Focused = it }
+ )
+ FilledTextField(
+ modifier = Modifier.testTag(textField3Tag),
+ value = "input2",
+ onValueChange = {},
+ label = {},
+ onFocusChange = { textField3Focused = it }
+ )
+ }
+ }
+
+ findByTag(textField1Tag).doClick()
+
+ runOnIdleCompose {
+ assertThat(textField1Focused).isTrue()
+ assertThat(textField2Focused).isFalse()
+ assertThat(textField3Focused).isFalse()
+ }
+
+ findByTag(textField2Tag).doClick()
+
+ runOnIdleCompose {
+ assertThat(textField1Focused).isFalse()
+ assertThat(textField2Focused).isTrue()
+ assertThat(textField3Focused).isFalse()
+ }
+
+ findByTag(textField3Tag).doClick()
+
+ runOnIdleCompose {
+ assertThat(textField1Focused).isFalse()
+ assertThat(textField2Focused).isFalse()
+ assertThat(textField3Focused).isTrue()
+ }
+ }
+
+ @Test
+ fun testFilledTextField_getFocus_whenClickedOnSurfaceArea() {
+ var focused = false
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "input",
+ onValueChange = {},
+ label = {},
+ onFocusChange = { focused = it }
+ )
+ }
+ }
+
+ // Click on (2, 2) which is Surface area and outside input area
+ findByTag(TextfieldTag).doGesture {
+ sendClick(Offset(2f, 2f))
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(focused).isTrue()
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_getFocus_whenClickedOnInternalArea() {
+ var focused = false
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "input",
+ onValueChange = {},
+ label = {},
+ onFocusChange = { focused = it }
+ )
+ }
+ }
+
+ // Click on (2, 2) which is a background area and outside input area
+ findByTag(TextfieldTag).doGesture {
+ sendClick(Offset(2f, 2f))
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(focused).isTrue()
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPosition_initial_withDefaultHeight() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(
+ text = "label",
+ fontSize = 10.sp,
+ modifier = Modifier
+ .onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ }
+ )
+ },
+ modifier = Modifier.preferredHeight(56.dp)
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // centered position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(labelPosition.value?.y).isEqualTo(
+ ((ExpectedMinimumTextFieldHeight.toIntPx() - labelSize.value!!.height) / 2f)
+ .roundToInt().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ // label is centered in 56.dp default container, plus additional 8.dp padding on top
+ val minimumHeight = ExpectedMinimumTextFieldHeight.toIntPx()
+ assertThat(labelPosition.value?.y).isEqualTo(
+ ((minimumHeight - labelSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPosition_initial_withCustomHeight() {
+ val height = 80.dp
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ modifier = Modifier.preferredHeight(height),
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // centered position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(labelPosition.value?.y).isEqualTo(
+ ((height.toIntPx() - labelSize.value!!.height) / 2f).roundToInt().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPosition_whenFocused() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ val baseline = Ref<Float>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ baseline.value =
+ it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
+ })
+ }
+ )
+ }
+ }
+
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // label's top position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(baseline.value).isEqualTo(
+ ExpectedBaselineOffset.toIntPx().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_labelPosition_whenFocused() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ })
+ }
+ )
+ }
+
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // label's top position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(labelPosition.value?.y).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPosition_whenInput() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ val baseline = Ref<Float>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ value = "input",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ baseline.value =
+ it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
+ })
+ }
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // label's top position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(baseline.value).isEqualTo(
+ ExpectedBaselineOffset.toIntPx().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_labelPosition_whenInput() {
+ val labelSize = Ref<IntSize>()
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ value = "input",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ labelSize.value = it.size
+ })
+ }
+ )
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(labelSize.value).isNotNull()
+ assertThat(labelSize.value?.height).isGreaterThan(0)
+ assertThat(labelSize.value?.width).isGreaterThan(0)
+ // label's top position
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(labelPosition.value?.y).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testFilledTextField_placeholderPosition_withLabel() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ val placeholderBaseline = Ref<Float>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ modifier = Modifier
+ .preferredHeight(60.dp)
+ .testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = { Text("label") },
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.globalPosition
+ placeholderSize.value = it.size
+ placeholderBaseline.value =
+ it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
+ })
+ }
+ )
+ }
+ }
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(placeholderSize.value).isNotNull()
+ assertThat(placeholderSize.value?.height).isGreaterThan(0)
+ assertThat(placeholderSize.value?.width).isGreaterThan(0)
+ // placeholder's position
+ assertThat(placeholderPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(placeholderBaseline.value)
+ .isEqualTo(
+ 60.dp.toIntPx().toFloat() -
+ ExpectedLastBaselineOffset.toIntPx().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_placeholderPosition_withLabel() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = { Text("label") },
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.globalPosition
+ placeholderSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(placeholderSize.value).isNotNull()
+ assertThat(placeholderSize.value?.height).isGreaterThan(0)
+ assertThat(placeholderSize.value?.width).isGreaterThan(0)
+ // placeholder's position
+ assertThat(placeholderPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ // placeholder is centered in 56.dp default container,
+ // plus additional 8.dp padding on top
+ assertThat(placeholderPosition.value?.y).isEqualTo(
+ ((ExpectedMinimumTextFieldHeight.toIntPx() - placeholderSize.value!!.height) /
+ 2f).roundToInt() +
+ 8.dp.toIntPx()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_placeholderPosition_whenNoLabel() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ val placeholderBaseline = Ref<Float>()
+ val height = 60.dp
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ modifier = Modifier.preferredHeight(height).testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.positionInRoot
+ placeholderSize.value = it.size
+ placeholderBaseline.value =
+ it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
+ })
+ }
+ )
+ }
+ }
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(placeholderSize.value).isNotNull()
+ assertThat(placeholderSize.value?.height).isGreaterThan(0)
+ assertThat(placeholderSize.value?.width).isGreaterThan(0)
+ // centered position
+ assertThat(placeholderPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ assertThat(placeholderPosition.value?.y).isEqualTo(
+ ((height.toIntPx() - placeholderSize.value!!.height) / 2f).roundToInt().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_placeholderPosition_whenNoLabel() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.globalPosition
+ placeholderSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ // size
+ assertThat(placeholderSize.value).isNotNull()
+ assertThat(placeholderSize.value?.height).isGreaterThan(0)
+ assertThat(placeholderSize.value?.width).isGreaterThan(0)
+ // centered position
+ assertThat(placeholderPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ // placeholder is centered in 56.dp default container,
+ // plus additional 8.dp padding on top
+ assertThat(placeholderPosition.value?.y).isEqualTo(
+ ((ExpectedMinimumTextFieldHeight.toIntPx() - placeholderSize.value!!.height) /
+ 2f).roundToInt() +
+ 8.dp.toIntPx()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_noPlaceholder_whenInputNotEmpty() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Column {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "input",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.globalPosition
+ placeholderSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(placeholderSize.value).isNull()
+ assertThat(placeholderPosition.value).isNull()
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_noPlaceholder_whenInputNotEmpty() {
+ val placeholderSize = Ref<IntSize>()
+ val placeholderPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Column {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "input",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text(text = "placeholder", modifier = Modifier.onPositioned {
+ placeholderPosition.value = it.globalPosition
+ placeholderSize.value = it.size
+ })
+ }
+ )
+ }
+ }
+
+ // click to focus
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(placeholderSize.value).isNull()
+ assertThat(placeholderPosition.value).isNull()
+ }
+ }
+
+ @Test
+ fun testFilledTextField_placeholderColorAndTextStyle() {
+ testRule.setMaterialContent {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text("placeholder")
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
+ assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
+ }
+ )
+ }
+
+ // click to focus
+ findByTag(TextfieldTag).doClick()
+ }
+
+ @Test
+ fun testOutlinedTextField_placeholderColorAndTextStyle() {
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ placeholder = {
+ Text("placeholder")
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
+ assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
+ }
+ )
+ }
+
+ // click to focus
+ findByTag(TextfieldTag).doClick()
+ }
+
+ @Test
+ fun testFilledTextField_trailingAndLeading_sizeAndPosition() {
+ val textFieldHeight = 60.dp
+ val textFieldWidth = 300.dp
+ val size = 30.dp
+ val leadingPosition = Ref<Offset>()
+ val leadingSize = Ref<IntSize>()
+ val trailingPosition = Ref<Offset>()
+ val trailingSize = Ref<IntSize>()
+
+ testRule.setMaterialContent {
+ FilledTextField(
+ value = "text",
+ onValueChange = {},
+ modifier = Modifier.preferredSize(textFieldWidth, textFieldHeight),
+ label = {},
+ leadingIcon = {
+ Box(Modifier.preferredSize(size).onPositioned {
+ leadingPosition.value = it.globalPosition
+ leadingSize.value = it.size
+ })
+ },
+ trailingIcon = {
+ Box(Modifier.preferredSize(size).onPositioned {
+ trailingPosition.value = it.globalPosition
+ trailingSize.value = it.size
+ })
+ }
+ )
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ // leading
+ assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+ assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
+ assertThat(leadingPosition.value?.y).isEqualTo(
+ ((textFieldHeight.toIntPx() - leadingSize.value!!.height) / 2f).roundToInt()
+ .toFloat()
+ )
+ // trailing
+ assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+ assertThat(trailingPosition.value?.x).isEqualTo(
+ (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
+ .toFloat()
+ )
+ assertThat(trailingPosition.value?.y)
+ .isEqualTo(
+ ((textFieldHeight.toIntPx() - trailingSize.value!!.height) / 2f)
+ .roundToInt().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_trailingAndLeading_sizeAndPosition() {
+ val textFieldWidth = 300.dp
+ val size = 30.dp
+ val leadingPosition = Ref<Offset>()
+ val leadingSize = Ref<IntSize>()
+ val trailingPosition = Ref<Offset>()
+ val trailingSize = Ref<IntSize>()
+
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ value = "text",
+ onValueChange = {},
+ modifier = Modifier.preferredWidth(textFieldWidth),
+ label = {},
+ leadingIcon = {
+ Box(Modifier.preferredSize(size).onPositioned {
+ leadingPosition.value = it.globalPosition
+ leadingSize.value = it.size
+ })
+ },
+ trailingIcon = {
+ Box(Modifier.preferredSize(size).onPositioned {
+ trailingPosition.value = it.globalPosition
+ trailingSize.value = it.size
+ })
+ }
+ )
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ val minimumHeight = ExpectedMinimumTextFieldHeight.toIntPx()
+ // leading
+ assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+ assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
+ assertThat(leadingPosition.value?.y).isEqualTo(
+ ((minimumHeight - leadingSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+ )
+ // trailing
+ assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+ assertThat(trailingPosition.value?.x).isEqualTo(
+ (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
+ .toFloat()
+ )
+ assertThat(trailingPosition.value?.y).isEqualTo(
+ ((minimumHeight - trailingSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPositionX_initial_withTrailingAndLeading() {
+ val height = 60.dp
+ val iconSize = 30.dp
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ modifier = Modifier.preferredHeight(height),
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ })
+ },
+ trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
+ leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(labelPosition.value?.x).isEqualTo(
+ (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
+ .toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_labelPositionX_initial_withTrailingAndLeading() {
+ val iconSize = 30.dp
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ })
+ },
+ trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
+ leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(labelPosition.value?.x).isEqualTo(
+ (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
+ .toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testFilledTextField_labelPositionX_initial_withEmptyTrailingAndLeading() {
+ val height = 60.dp
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ modifier = Modifier.preferredHeight(height),
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ })
+ },
+ trailingIcon = {},
+ leadingIcon = {}
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_labelPositionX_initial_withEmptyTrailingAndLeading() {
+ val labelPosition = Ref<Offset>()
+ testRule.setMaterialContent {
+ Box {
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ label = {
+ Text(text = "label", modifier = Modifier.onPositioned {
+ labelPosition.value = it.globalPosition
+ })
+ },
+ trailingIcon = {},
+ leadingIcon = {}
+ )
+ }
+ }
+
+ testRule.runOnIdleComposeWithDensity {
+ assertThat(labelPosition.value?.x).isEqualTo(
+ ExpectedPadding.toIntPx().toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testTextField_colorInLeadingTrailing_whenValidInput() {
+ testRule.setMaterialContent {
+ Column {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ label = {},
+ isErrorValue = false,
+ leadingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ },
+ trailingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ }
+ )
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ label = {},
+ isErrorValue = false,
+ leadingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ },
+ trailingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTextField_colorInLeadingTrailing_whenInvalidInput() {
+ testRule.setMaterialContent {
+ Column {
+ FilledTextField(
+ value = "",
+ onValueChange = {},
+ label = {},
+ isErrorValue = true,
+ leadingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ },
+ trailingIcon = {
+ assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
+ }
+ )
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ label = {},
+ isErrorValue = true,
+ leadingIcon = {
+ assertThat(contentColor())
+ .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+ },
+ trailingIcon = {
+ assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testFilledTextField_imeActionAndKeyboardTypePropagatedDownstream() {
+ val textInputService = mock<TextInputService>()
+ testRule.setContent {
+ Providers(
+ TextInputServiceAmbient provides textInputService
+ ) {
+ var text = state { TextFieldValue("") }
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = text.value,
+ onValueChange = { text.value = it },
+ label = {},
+ imeAction = ImeAction.Go,
+ keyboardType = KeyboardType.Email
+ )
+ }
+ }
+
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ runOnIdleCompose {
+ verify(textInputService, atLeastOnce()).startInput(
+ value = any(),
+ keyboardType = eq(KeyboardType.Email),
+ imeAction = eq(ImeAction.Go),
+ onEditCommand = any(),
+ onImeActionPerformed = any()
+ )
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_imeActionAndKeyboardTypePropagatedDownstream() {
+ val textInputService = mock<TextInputService>()
+ testRule.setContent {
+ Providers(
+ TextInputServiceAmbient provides textInputService
+ ) {
+ var text = state { TextFieldValue("") }
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = text.value,
+ onValueChange = { text.value = it },
+ label = {},
+ imeAction = ImeAction.Go,
+ keyboardType = KeyboardType.Email
+ )
+ }
+ }
+
+ clickAndAdvanceClock(TextfieldTag, 200)
+
+ runOnIdleCompose {
+ verify(textInputService, atLeastOnce()).startInput(
+ value = any(),
+ keyboardType = eq(KeyboardType.Email),
+ imeAction = eq(ImeAction.Go),
+ onEditCommand = any(),
+ onImeActionPerformed = any()
+ )
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun testFilledTextField_visualTransformationPropagated() {
+ testRule.setMaterialContent {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "qwerty",
+ onValueChange = {},
+ label = {},
+ visualTransformation = PasswordVisualTransformation('\u0020'),
+ backgroundColor = Color.White,
+ shape = RectangleShape
+ )
+ }
+
+ findByTag(TextfieldTag)
+ .captureToBitmap()
+ .assertShape(
+ density = testRule.density,
+ backgroundColor = Color.White,
+ shapeColor = Color.White,
+ shape = RectangleShape,
+ // avoid elevation artifacts
+ shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun testOutlinedTextField_visualTransformationPropagated() {
+ testRule.setMaterialContent {
+ Box(Modifier.drawBackground(Color.White)) {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "qwerty",
+ onValueChange = {},
+ label = {},
+ visualTransformation = PasswordVisualTransformation('\u0020')
+ )
+ }
+ }
+
+ findByTag(TextfieldTag)
+ .captureToBitmap()
+ .assertShape(
+ density = testRule.density,
+ backgroundColor = Color.White,
+ shapeColor = Color.White,
+ shape = RectangleShape,
+ // avoid elevation artifacts
+ shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun testFilledTextField_alphaNotSet_toBackgroundColorAndTransparentColors() {
+ val latch = CountDownLatch(1)
+
+ testRule.setMaterialContent {
+ Stack(Modifier.drawBackground(Color.White)) {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ shape = RectangleShape,
+ backgroundColor = Color.Blue,
+ activeColor = Color.Transparent,
+ inactiveColor = Color.Transparent,
+ onFocusChange = { focused ->
+ if (focused) latch.countDown()
+ }
+ )
+ }
+ }
+
+ val expectedColor = Color.Blue.copy(alpha = 0.12f).compositeOver(Color.White)
+
+ findByTag(TextfieldTag)
+ .captureToBitmap()
+ .assertShape(
+ density = testRule.density,
+ backgroundColor = Color.White,
+ shapeColor = expectedColor,
+ shape = RectangleShape,
+ // avoid elevation artifacts
+ shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
+ )
+
+ findByTag(TextfieldTag).doClick()
+ assert(latch.await(1, TimeUnit.SECONDS))
+
+ findByTag(TextfieldTag)
+ .captureToBitmap()
+ .assertShape(
+ density = testRule.density,
+ backgroundColor = Color.White,
+ shapeColor = expectedColor,
+ shape = RectangleShape,
+ // avoid elevation artifacts
+ shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
+ )
+ }
+
+ @Test
+ fun testFilledTextField_onTextInputStartedCallback() {
+ var controller: SoftwareKeyboardController? = null
+
+ testRule.setMaterialContent {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ onTextInputStarted = {
+ controller = it
+ }
+ )
+ }
+ assertThat(controller).isNull()
+
+ findByTag(TextfieldTag)
+ .doClick()
+
+ runOnIdleCompose {
+ assertThat(controller).isNotNull()
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_onTextInputStartedCallback() {
+ var controller: SoftwareKeyboardController? = null
+
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ onTextInputStarted = {
+ controller = it
+ }
+ )
+ }
+ assertThat(controller).isNull()
+
+ findByTag(TextfieldTag)
+ .doClick()
+
+ runOnIdleCompose {
+ assertThat(controller).isNotNull()
+ }
+ }
+
+ @Test
+ fun testFilledTextField_imeActionCallback_withSoftwareKeyboardController() {
+ var controller: SoftwareKeyboardController? = null
+
+ testRule.setMaterialContent {
+ FilledTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ imeAction = ImeAction.Go,
+ onImeActionPerformed = { _, softwareKeyboardController ->
+ controller = softwareKeyboardController
+ }
+ )
+ }
+ assertThat(controller).isNull()
+
+ findByTag(TextfieldTag)
+ .doSendImeAction()
+
+ runOnIdleCompose {
+ assertThat(controller).isNotNull()
+ }
+ }
+
+ @Test
+ fun testOutlinedTextField_imeActionCallback_withSoftwareKeyboardController() {
+ var controller: SoftwareKeyboardController? = null
+
+ testRule.setMaterialContent {
+ OutlinedTextField(
+ modifier = Modifier.testTag(TextfieldTag),
+ value = "",
+ onValueChange = {},
+ label = {},
+ imeAction = ImeAction.Go,
+ onImeActionPerformed = { _, softwareKeyboardController ->
+ controller = softwareKeyboardController
+ }
+ )
+ }
+ assertThat(controller).isNull()
+
+ findByTag(TextfieldTag)
+ .doSendImeAction()
+
+ runOnIdleCompose {
+ assertThat(controller).isNotNull()
+ }
+ }
+
+ @Test
+ fun testTextField_scrollable_withLongInput() {
+ val scrollerPosition = TextFieldScrollerPosition()
+ testRule.setContent {
+ Stack {
+ TextFieldScroller(
+ remember { scrollerPosition },
+ Modifier.fillMaxWidth().preferredHeight(40.dp)
+ ) {
+ TextField(
+ value = TextFieldValue(LONG_TEXT),
+ onValueChange = {}
+ )
+ }
+ }
+ }
+
+ runOnIdleCompose {
+ assertThat(scrollerPosition.maximum).isLessThan(Float.POSITIVE_INFINITY)
+ assertThat(scrollerPosition.maximum).isGreaterThan(0f)
+ }
+ }
+
+ @Test
+ fun testTextField_notScrollable_withShortInput() {
+ val text = "text"
+ val scrollerPosition = TextFieldScrollerPosition()
+ testRule.setContent {
+ Stack {
+ TextFieldScroller(
+ remember { scrollerPosition },
+ Modifier.fillMaxWidth().preferredHeight(100.dp)
+ ) {
+ TextField(
+ value = TextFieldValue(text),
+ onValueChange = {}
+ )
+ }
+ }
+ }
+
+ runOnIdleCompose {
+ assertThat(scrollerPosition.maximum).isEqualTo(0f)
+ }
+ }
+
+ @Test
+ fun testTextField_scrolledAndClipped() {
+ val scrollerPosition = TextFieldScrollerPosition()
+
+ val parentSize = 200
+ val textFieldSize = 50
+
+ with(testRule.density) {
+ testRule.setContent {
+ Stack(
+ Modifier
+ .preferredSize(parentSize.toDp())
+ .drawBackground(Color.White)
+ .testTag(TextfieldTag)
+ ) {
+ TextFieldScroller(
+ remember { scrollerPosition },
+ Modifier.preferredSize(textFieldSize.toDp())
+ ) {
+ TextField(
+ value = TextFieldValue(LONG_TEXT),
+ onValueChange = {}
+ )
+ }
+ }
+ }
+ }
+
+ runOnIdleCompose {}
+
+ findByTag(TextfieldTag)
+ .captureToBitmap()
+ .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
+ if (position.x > textFieldSize && position.y > textFieldSize) Color.White else null
+ }
+ }
+
+ @Test
+ fun testTextField_swipe_whenLongInput() {
+ val scrollerPosition = TextFieldScrollerPosition()
+
+ testRule.setContent {
+ Stack {
+ TextFieldScroller(
+ remember { scrollerPosition },
+ Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(TextfieldTag)
+ ) {
+ TextField(
+ value = TextFieldValue(LONG_TEXT),
+ onValueChange = {}
+ )
+ }
+ }
+ }
+
+ runOnIdleCompose {
+ assertThat(scrollerPosition.current).isEqualTo(0f)
+ }
+
+ findByTag(TextfieldTag)
+ .doGesture { sendSwipeDown() }
+
+ val firstSwipePosition = runOnIdleCompose {
+ scrollerPosition.current
+ }
+ assertThat(firstSwipePosition).isGreaterThan(0f)
+
+ findByTag(TextfieldTag)
+ .doGesture { sendSwipeUp() }
+ runOnIdleCompose {
+ assertThat(scrollerPosition.current).isLessThan(firstSwipePosition)
+ }
+ }
+
+ @Test
+ fun textFieldScroller_restoresScrollerPosition() {
+ val restorationTester = StateRestorationTester(testRule)
+ var scrollerPosition = TextFieldScrollerPosition()
+
+ restorationTester.setContent {
+ scrollerPosition = rememberSavedInstanceState(
+ saver = TextFieldScrollerPosition.Saver
+ ) {
+ TextFieldScrollerPosition()
+ }
+ TextFieldScroller(
+ scrollerPosition,
+ Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(TextfieldTag)
+ ) {
+ TextField(
+ value = TextFieldValue(LONG_TEXT),
+ onValueChange = {}
+ )
+ }
+ }
+
+ findByTag(TextfieldTag)
+ .doGesture { sendSwipeDown() }
+
+ val swipePosition = runOnIdleCompose {
+ scrollerPosition.current
+ }
+ assertThat(swipePosition).isGreaterThan(0f)
+
+ runOnIdleCompose {
+ scrollerPosition = TextFieldScrollerPosition()
+ assertThat(scrollerPosition.current).isEqualTo(0f)
+ }
+
+ restorationTester.emulateSavedInstanceStateRestore()
+
+ runOnIdleCompose {
+ assertThat(scrollerPosition.current).isEqualTo(swipePosition)
+ }
+ }
+
+ private fun clickAndAdvanceClock(tag: String, time: Long) {
+ findByTag(tag).doClick()
+ waitForIdle()
+ testRule.clockTestRule.pauseClock()
+ testRule.clockTestRule.advanceClock(time)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt b/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
index e0befcc..df4c566 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
@@ -63,9 +63,12 @@
import androidx.ui.foundation.gestures.DragDirection
import androidx.ui.foundation.gestures.ScrollableState
import androidx.ui.foundation.gestures.scrollable
+import androidx.ui.foundation.drawBackground
import androidx.ui.foundation.shape.corner.ZeroCornerSize
import androidx.ui.geometry.Offset
import androidx.ui.graphics.Color
+import androidx.ui.graphics.Path
+import androidx.ui.graphics.RectangleShape
import androidx.ui.graphics.Shape
import androidx.ui.graphics.drawscope.Stroke
import androidx.ui.input.ImeAction
@@ -90,8 +93,8 @@
import kotlin.math.roundToInt
/**
- * Material Design implementation of the
- * [Material Filled TextField](https://material.io/components/text-fields/#filled-text-field)
+ * Material Design implementation of a
+ * [Filled TextField](https://material.io/components/text-fields/#filled-text-field)
*
* A simple example looks like:
*
@@ -122,14 +125,14 @@
* @sample androidx.ui.material.samples.TextFieldWithHideKeyboardOnImeAction
*
* If apart from input text change you also want to observe the cursor location or selection range,
- * use a FilledTextField overload with the [TextFieldValue] parameter instead.
+ * use the FilledTextField overload with the [TextFieldValue] parameter instead.
*
* @param value the input text to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param label the label to be displayed inside the text field container. The default text style
* for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when text field is not in focus
+ * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
@@ -139,7 +142,6 @@
* container
* @param trailingIcon the optional trailing icon to be displayed at the end of the text field
* container
- * If the boolean parameter value is `true`, it means the text field has a focus, and vice versa
* @param isErrorValue indicates if the text field's current value is in error. If set to true, the
* label, bottom indicator and trailing icon will be displayed in [errorColor] color
* @param keyboardType the keyboard type to be used with the text field.
@@ -156,15 +158,16 @@
* @param visualTransformation transforms the visual representation of the input [value].
* For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
* text field. By default no visual transformation is applied
- * @param onFocusChange a callback to be invoked when the text field gets or loses the focus
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
* @param onTextInputStarted a callback to be invoked when the connection with the platform's text
* input service (e.g. software keyboard on Android) has been established. Called with the
* [SoftwareKeyboardController] instance that can be used to request to show or hide the software
* keyboard
* @param activeColor the color of the label, bottom indicator and the cursor when the text field is
* in focus
- * @param inactiveColor the color of the input text or placeholder when the text field is in
- * focus, and the color of label and bottom indicator when the text field is not in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
* @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
* used when [isErrorValue] is set to true
* @param backgroundColor the background color of the text field's container. To the color provided
@@ -203,7 +206,8 @@
)
textFieldValue = TextFieldValue(text = value, selection = newSelection)
}
- FilledTextFieldImpl(
+ TextFieldImpl(
+ type = TextFieldType.Filled,
value = textFieldValue,
onValueChange = {
val previousValue = textFieldValue.text
@@ -234,14 +238,14 @@
}
/**
- * Material Design implementation of the
- * [Material Filled TextField](https://material.io/components/text-fields/#filled-text-field)
- *
+ * Material Design implementation of a
+ * [Filled TextField](https://material.io/components/text-fields/#filled-text-field)
*
* See example usage:
* @sample androidx.ui.material.samples.FilledTextFieldSample
*
- * If you only want to observe an input text change, use a FilledTextField overload with the
+ * This overload provides access to the input text, cursor position and selection range. If you
+ * only want to observe an input text change, use the FilledTextField overload with the
* [String] parameter instead.
*
* @param value the input [TextFieldValue] to be shown in the text field
@@ -249,7 +253,7 @@
* selection or cursor. An updated [TextFieldValue] comes as a parameter of the callback
* @param label the label to be displayed inside the text field container. The default text style
* for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when text field is not in focus
+ * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
@@ -259,7 +263,6 @@
* container
* @param trailingIcon the optional trailing icon to be displayed at the end of the text field
* container
- * If the boolean parameter value is `true`, it means the text field has a focus, and vice versa
* @param isErrorValue indicates if the text field's current value is in error state. If set to
* true, the label, bottom indicator and trailing icon will be displayed in [errorColor] color
* @param keyboardType the keyboard type to be used with the text field.
@@ -276,15 +279,16 @@
* @param visualTransformation transforms the visual representation of the input [value].
* For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
* text field. By default no visual transformation is applied
- * @param onFocusChange a callback to be invoked when the text field gets or loses the focus
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
* @param onTextInputStarted a callback to be invoked when the connection with the platform's text
* input service (e.g. software keyboard on Android) has been established. Called with the
* [SoftwareKeyboardController] instance that can be used to request to show or hide the software
* keyboard
* @param activeColor the color of the label, bottom indicator and the cursor when the text field is
* in focus
- * @param inactiveColor the color of the input text or placeholder when the text field is in
- * focus, and the color of label and bottom indicator when the text field is not in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
* @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
* used when [isErrorValue] is set to true
* @param backgroundColor the background color of the text field's container. To the color provided
@@ -315,7 +319,8 @@
shape: Shape =
MaterialTheme.shapes.small.copy(bottomLeft = ZeroCornerSize, bottomRight = ZeroCornerSize)
) {
- FilledTextFieldImpl(
+ TextFieldImpl(
+ type = TextFieldType.Filled,
value = value,
onValueChange = onValueChange,
modifier = modifier,
@@ -390,7 +395,8 @@
)
}
}
- FilledTextFieldImpl(
+ TextFieldImpl(
+ type = TextFieldType.Filled,
value = fullModel.value,
onValueChange = onValueChangeWrapper,
modifier = modifier,
@@ -415,10 +421,231 @@
}
/**
- * Implementation of the [FilledTextField]
+ * Material Design implementation of an
+ * [Outlined TextField](https://material.io/components/text-fields/#outlined-text-field)
+ *
+ * See example usage:
+ * @sample androidx.ui.material.samples.SimpleOutlinedTextFieldSample
+ *
+ * If apart from input text change you also want to observe the cursor location or selection range,
+ * use the OutlinedTextField overload with the [TextFieldValue] parameter instead.
+ *
+ * @param value the input text to be shown in the text field
+ * @param onValueChange the callback that is triggered when the input service updates the text. An
+ * updated text comes as a parameter of the callback
+ * @param label the label to be displayed inside the text field container. The default text style
+ * for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
+ * @param modifier a [Modifier] for this text field
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [currentTextStyle] defined by the theme
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container
+ * @param isErrorValue indicates if the text field's current value is in error. If set to true, the
+ * label, bottom indicator and trailing icon will be displayed in [errorColor] color
+ * @param keyboardType the keyboard type to be used with the text field.
+ * Note that the input type is not guaranteed. For example, an IME may send a non-ASCII character
+ * even if you set the keyboard type to [KeyboardType.Ascii]
+ * @param imeAction the IME action honored by the IME. The 'enter' key on the soft keyboard input
+ * will show a corresponding icon. For example, search icon may be shown if [ImeAction.Search] is
+ * selected. When a user taps on that 'enter' key, the [onImeActionPerformed] callback is called
+ * with the specified [ImeAction]
+ * @param onImeActionPerformed is triggered when the input service performs an [ImeAction].
+ * Note that the emitted IME action may be different from what you specified through the
+ * [imeAction] field. The callback also exposes a [SoftwareKeyboardController] instance as a
+ * parameter that can be used to request to hide the software keyboard
+ * @param visualTransformation transforms the visual representation of the input [value].
+ * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
+ * text field. By default no visual transformation is applied
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
+ * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
+ * input service (e.g. software keyboard on Android) has been established. Called with the
+ * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
+ * keyboard
+ * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
+ * in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
+ * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
+ * used when [isErrorValue] is set to true
*/
@Composable
-private fun FilledTextFieldImpl(
+fun OutlinedTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ textStyle: TextStyle = currentTextStyle(),
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ isErrorValue: Boolean = false,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Unspecified,
+ onImeActionPerformed: (ImeAction, SoftwareKeyboardController?) -> Unit = { _, _ -> },
+ onFocusChange: (Boolean) -> Unit = {},
+ onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+ activeColor: Color = MaterialTheme.colors.primary,
+ inactiveColor: Color = MaterialTheme.colors.onSurface,
+ errorColor: Color = MaterialTheme.colors.error
+) {
+ var textFieldValue by state { TextFieldValue() }
+ if (textFieldValue.text != value) {
+ val newSelection = TextRange(
+ textFieldValue.selection.start.coerceIn(0, value.length),
+ textFieldValue.selection.end.coerceIn(0, value.length)
+ )
+ textFieldValue = TextFieldValue(text = value, selection = newSelection)
+ }
+
+ TextFieldImpl(
+ type = TextFieldType.Outlined,
+ value = textFieldValue,
+ onValueChange = {
+ val previousValue = textFieldValue.text
+ textFieldValue = it
+ if (previousValue != it.text) {
+ onValueChange(it.text)
+ }
+ },
+ modifier = modifier,
+ textStyle = textStyle,
+ label = label,
+ placeholder = placeholder,
+ leading = leadingIcon,
+ trailing = trailingIcon,
+ isErrorValue = isErrorValue,
+ visualTransformation = visualTransformation,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ onImeActionPerformed = onImeActionPerformed,
+ onFocusChange = onFocusChange,
+ onTextInputStarted = onTextInputStarted,
+ activeColor = activeColor,
+ inactiveColor = inactiveColor,
+ errorColor = errorColor,
+ backgroundColor = Color.Unset,
+ shape = RectangleShape
+ )
+}
+
+/**
+ * Material Design implementation of an
+ * [Outlined TextField](https://material.io/components/text-fields/#outlined-text-field)
+ *
+ * See example usage:
+ * @sample androidx.ui.material.samples.OutlinedTextFieldSample
+ *
+ * This overload provides access to the input text, cursor position and selection range. If you
+ * only want to observe an input text change, use the OutlinedTextField overload with the
+ * [String] parameter instead.
+ *
+ * @param value the input [TextFieldValue] to be shown in the text field
+ * @param onValueChange the callback that is triggered when the input service updates the text,
+ * selection or cursor. An updated [TextFieldValue] comes as a parameter of the callback
+ * @param label the label to be displayed inside the text field container. The default text style
+ * for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
+ * @param modifier a [Modifier] for this text field
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [currentTextStyle] defined by the theme
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container
+ * @param isErrorValue indicates if the text field's current value is in error state. If set to
+ * true, the label, bottom indicator and trailing icon will be displayed in [errorColor] color
+ * @param keyboardType the keyboard type to be used with the text field.
+ * Note that the input type is not guaranteed. For example, an IME may send a non-ASCII character
+ * even if you set the keyboard type to [KeyboardType.Ascii]
+ * @param imeAction the IME action honored by the IME. The 'enter' key on the soft keyboard input
+ * will show a corresponding icon. For example, search icon may be shown if [ImeAction.Search] is
+ * selected. When a user taps on that 'enter' key, the [onImeActionPerformed] callback is called
+ * with the specified [ImeAction]
+ * @param onImeActionPerformed is triggered when the input service performs an [ImeAction].
+ * Note that the emitted IME action may be different from what you specified through the
+ * [imeAction] field. The callback also exposes a [SoftwareKeyboardController] instance as a
+ * parameter that can be used to request to hide the software keyboard
+ * @param visualTransformation transforms the visual representation of the input [value].
+ * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
+ * text field. By default no visual transformation is applied
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
+ * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
+ * input service (e.g. software keyboard on Android) has been established. Called with the
+ * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
+ * keyboard
+ * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
+ * in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
+ * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
+ * used when [isErrorValue] is set to true
+ */
+@Composable
+fun OutlinedTextField(
+ value: TextFieldValue,
+ onValueChange: (TextFieldValue) -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ textStyle: TextStyle = currentTextStyle(),
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ isErrorValue: Boolean = false,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Unspecified,
+ onImeActionPerformed: (ImeAction, SoftwareKeyboardController?) -> Unit = { _, _ -> },
+ onFocusChange: (Boolean) -> Unit = {},
+ onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+ activeColor: Color = MaterialTheme.colors.primary,
+ inactiveColor: Color = MaterialTheme.colors.onSurface,
+ errorColor: Color = MaterialTheme.colors.error
+) {
+ TextFieldImpl(
+ type = TextFieldType.Outlined,
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier,
+ textStyle = textStyle,
+ label = label,
+ placeholder = placeholder,
+ leading = leadingIcon,
+ trailing = trailingIcon,
+ isErrorValue = isErrorValue,
+ visualTransformation = visualTransformation,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ onImeActionPerformed = onImeActionPerformed,
+ onFocusChange = onFocusChange,
+ onTextInputStarted = onTextInputStarted,
+ activeColor = activeColor,
+ inactiveColor = inactiveColor,
+ errorColor = errorColor,
+ backgroundColor = Color.Unset,
+ shape = RectangleShape
+ )
+}
+
+private enum class TextFieldType {
+ Filled, Outlined
+}
+
+/**
+ * Implementation of the [FilledTextField] and [OutlinedTextField]
+ */
+@Composable
+private fun TextFieldImpl(
+ type: TextFieldType,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier,
@@ -479,7 +706,7 @@
) {
TextField(
value = value,
- modifier = tagModifier + focusModifier,
+ modifier = focusModifier,
textStyle = textStyle,
onValueChange = onValueChange,
onFocusChange = onFocusChange,
@@ -499,84 +726,194 @@
}
}
- val textFieldModifier = Modifier
- .preferredSizeIn(
- minWidth = TextFieldMinWidth,
- minHeight = TextFieldMinHeight
- )
- .plus(modifier)
+ val textFieldModifier = modifier
.semantics(mergeAllDescendants = true)
+ .clickable(indication = RippleIndication(bounded = false)) {
+ focusModifier.requestFocus()
+ keyboardController.value?.showSoftwareKeyboard()
+ }
- Surface(
- modifier = textFieldModifier,
- shape = shape,
- color = backgroundColor.applyAlpha(alpha = ContainerAlpha)
- ) {
- val emphasisLevels = EmphasisAmbient.current
- val emphasizedActiveColor = emphasisLevels.high.applyEmphasis(activeColor)
- val labelInactiveColor = emphasisLevels.medium.applyEmphasis(inactiveColor)
- val indicatorInactiveColor =
- inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
+ val emphasisLevels = EmphasisAmbient.current
- TextFieldTransitionScope.transition(
- inputState = inputState.value,
- activeColor = emphasizedActiveColor,
- labelInactiveColor = labelInactiveColor,
- indicatorInactiveColor = indicatorInactiveColor
- ) { labelProgress, animatedLabelColor, indicatorWidth, indicatorColor ->
- // TODO(soboleva): figure out how this will play with the textStyle provided in label slot
+ TextFieldTransitionScope.transition(
+ inputState = inputState.value,
+ activeColor = if (isErrorValue) {
+ errorColor
+ } else {
+ emphasisLevels.high.applyEmphasis(activeColor)
+ },
+ labelInactiveColor = emphasisLevels.medium.applyEmphasis(inactiveColor),
+ indicatorInactiveColor = inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
+ ) { labelProgress, animatedLabelColor, indicatorWidth, animatedIndicatorColor ->
+
+ val leadingColor = inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
+ val trailingColor = if (isErrorValue) errorColor else leadingColor
+
+ val decoratedLabel = @Composable {
val labelAnimatedStyle = lerp(
MaterialTheme.typography.subtitle1,
MaterialTheme.typography.caption,
labelProgress
)
-
- val leadingColor = inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
- val trailingColor = if (isErrorValue) errorColor else leadingColor
-
- // text field with label and placeholder
- val labelColor = if (isErrorValue) errorColor else animatedLabelColor
- val decoratedLabel = @Composable {
- Decoration(
- contentColor = labelColor,
- typography = labelAnimatedStyle,
- children = label
- )
- }
-
- // places leading icon, text field with label, trailing icon
- IconsTextFieldLayout(
- modifier = Modifier
- .clickable(indication = RippleIndication(bounded = false)) {
- focusModifier.requestFocus()
- keyboardController.value?.showSoftwareKeyboard()
- }
- .drawIndicatorLine(
- lineWidth = indicatorWidth,
- color = if (isErrorValue) errorColor else indicatorColor
- ),
- textField = {
- TextFieldLayout(
- animationProgress = labelProgress,
- modifier = Modifier
- .padding(
- start = HorizontalTextFieldPadding,
- end = HorizontalTextFieldPadding
- ),
- placeholder = decoratedPlaceholder,
- label = decoratedLabel,
- textField = decoratedTextField
- )
- },
- leading = leading,
- trailing = trailing,
- leadingColor = leadingColor,
- trailingColor = trailingColor
+ Decoration(
+ contentColor = animatedLabelColor,
+ typography = labelAnimatedStyle,
+ children = label
)
}
+
+ when (type) {
+ TextFieldType.Filled -> {
+ FilledTextFieldLayout(
+ textFieldModifier = Modifier
+ .preferredSizeIn(
+ minWidth = TextFieldMinWidth,
+ minHeight = TextFieldMinHeight
+ )
+ .plus(textFieldModifier),
+ decoratedTextField = decoratedTextField,
+ decoratedPlaceholder = decoratedPlaceholder,
+ decoratedLabel = decoratedLabel,
+ leading = leading,
+ trailing = trailing,
+ leadingColor = leadingColor,
+ trailingColor = trailingColor,
+ labelProgress = labelProgress,
+ indicatorWidth = indicatorWidth,
+ indicatorColor = animatedIndicatorColor,
+ backgroundColor = backgroundColor,
+ shape = shape
+ )
+ }
+ TextFieldType.Outlined -> {
+ OutlinedTextFieldLayout(
+ textFieldModifier = Modifier
+ .preferredSizeIn(
+ minWidth = TextFieldMinWidth,
+ minHeight = TextFieldMinHeight + OutlinedTextFieldTopPadding
+ )
+ .plus(textFieldModifier)
+ .padding(top = OutlinedTextFieldTopPadding),
+ decoratedTextField = decoratedTextField,
+ decoratedPlaceholder = decoratedPlaceholder,
+ decoratedLabel = decoratedLabel,
+ leading = leading,
+ trailing = trailing,
+ leadingColor = leadingColor,
+ trailingColor = trailingColor,
+ labelProgress = labelProgress,
+ indicatorWidth = indicatorWidth,
+ indicatorColor = animatedIndicatorColor,
+ focusModifier = focusModifier,
+ emptyInput = value.text.isEmpty()
+ )
+ }
+ }
}
}
+@Composable
+private fun FilledTextFieldLayout(
+ textFieldModifier: Modifier = Modifier,
+ decoratedTextField: @Composable (Modifier) -> Unit,
+ decoratedPlaceholder: @Composable (() -> Unit)?,
+ decoratedLabel: @Composable () -> Unit,
+ leading: @Composable (() -> Unit)?,
+ trailing: @Composable (() -> Unit)?,
+ leadingColor: Color,
+ trailingColor: Color,
+ labelProgress: Float,
+ indicatorWidth: Dp,
+ indicatorColor: Color,
+ backgroundColor: Color,
+ shape: Shape
+) {
+ // places leading icon, text field with label and placeholder, trailing icon
+ FilledTextField.IconsWithTextFieldLayout(
+ modifier = textFieldModifier
+ .drawBackground(
+ color = backgroundColor.applyAlpha(alpha = ContainerAlpha),
+ shape = shape
+ )
+ .drawIndicatorLine(
+ lineWidth = indicatorWidth,
+ color = indicatorColor
+ ),
+ textField = @Composable {
+ // places input field, label, placeholder
+ FilledTextField.TextFieldWithLabelLayout(
+ animationProgress = labelProgress,
+ modifier = Modifier
+ .padding(
+ start = TextFieldPadding,
+ end = TextFieldPadding
+ ),
+ placeholder = decoratedPlaceholder,
+ label = decoratedLabel,
+ textField = decoratedTextField
+ )
+ },
+ leading = leading,
+ trailing = trailing,
+ leadingColor = leadingColor,
+ trailingColor = trailingColor
+ )
+}
+
+@Composable
+private fun OutlinedTextFieldLayout(
+ textFieldModifier: Modifier = Modifier,
+ decoratedTextField: @Composable (Modifier) -> Unit,
+ decoratedPlaceholder: @Composable (() -> Unit)?,
+ decoratedLabel: @Composable () -> Unit,
+ leading: @Composable (() -> Unit)?,
+ trailing: @Composable (() -> Unit)?,
+ leadingColor: Color,
+ trailingColor: Color,
+ labelProgress: Float,
+ indicatorWidth: Dp,
+ indicatorColor: Color,
+ focusModifier: FocusModifier,
+ emptyInput: Boolean
+) {
+ val outlinedBorderParams = remember {
+ OutlinedBorderParams(indicatorWidth, indicatorColor)
+ }
+ if (indicatorColor != outlinedBorderParams.color.value ||
+ indicatorWidth != outlinedBorderParams.borderWidth.value
+ ) {
+ outlinedBorderParams.color.value = indicatorColor
+ outlinedBorderParams.borderWidth.value = indicatorWidth
+ }
+
+ // places leading icon, input field, label, placeholder, trailing icon
+ OutlinedTextField.IconsWithTextFieldLayout(
+ modifier = textFieldModifier.drawOutlinedBorder(outlinedBorderParams),
+ textField = decoratedTextField,
+ leading = leading,
+ trailing = trailing,
+ leadingColor = leadingColor,
+ trailingColor = trailingColor,
+ onLabelMeasured = {
+ val newLabelWidth = it * labelProgress
+
+ val labelWidth = when {
+ focusModifier.focusState == FocusState.Focused -> newLabelWidth
+ !emptyInput -> newLabelWidth
+ focusModifier.focusState == FocusState.NotFocused && emptyInput -> newLabelWidth
+ else -> 0f
+ }
+
+ if (outlinedBorderParams.labelWidth.value != labelWidth) {
+ outlinedBorderParams.labelWidth.value = labelWidth
+ }
+ },
+ animationProgress = labelProgress,
+ placeholder = decoratedPlaceholder,
+ label = decoratedLabel
+ )
+}
+
/**
* Similar to [androidx.ui.foundation.VerticalScroller] but does not lose the minWidth constraints.
*/
@@ -662,152 +999,444 @@
if (typography != null) ProvideTextStyle(typography, colorAndEmphasis) else colorAndEmphasis()
}
-/**
- * Layout of the text field, label and placeholder
- */
-@Composable
-private fun TextFieldLayout(
- animationProgress: Float,
- modifier: Modifier,
- placeholder: @Composable (() -> Unit)?,
- label: @Composable () -> Unit,
- textField: @Composable (Modifier) -> Unit
-) {
- Layout(
- children = {
- if (placeholder != null) {
- Box(modifier = Modifier.tag(PlaceholderTag), children = placeholder)
+private object FilledTextField {
+ /**
+ * Layout of the text field, label and placeholder part in [FilledTextField]
+ */
+ @Composable
+ fun TextFieldWithLabelLayout(
+ animationProgress: Float,
+ modifier: Modifier,
+ placeholder: @Composable (() -> Unit)?,
+ label: @Composable () -> Unit,
+ textField: @Composable (Modifier) -> Unit
+ ) {
+ Layout(
+ children = {
+ if (placeholder != null) {
+ Box(modifier = Modifier.tag(PlaceholderTag), children = placeholder)
+ }
+ Box(modifier = Modifier.tag(LabelTag), children = label)
+ textField(Modifier.tag(TextFieldTag))
+ },
+ modifier = modifier
+ ) { measurables, constraints, _ ->
+ val placeholderPlaceable =
+ measurables.find { it.tag == PlaceholderTag }?.measure(constraints)
+
+ val baseLineOffset = FirstBaselineOffset.toIntPx()
+
+ val labelConstraints = constraints
+ .offset(vertical = -LastBaselineOffset.toIntPx())
+ .copy(minWidth = 0, minHeight = 0)
+ val labelPlaceable = measurables.first { it.tag == LabelTag }.measure(labelConstraints)
+ val labelBaseline = labelPlaceable[LastBaseline] ?: labelPlaceable.height
+ val labelEndPosition = (baseLineOffset - labelBaseline).coerceAtLeast(0)
+ val effectiveLabelBaseline = max(labelBaseline, baseLineOffset)
+
+ val textFieldConstraints = constraints
+ .offset(vertical = -LastBaselineOffset.toIntPx() - effectiveLabelBaseline)
+ .copy(minHeight = 0)
+ val textfieldPlaceable = measurables
+ .first { it.tag == TextFieldTag }
+ .measure(textFieldConstraints)
+ val textfieldLastBaseline = requireNotNull(textfieldPlaceable[LastBaseline]) {
+ "No text last baseline."
}
- Box(modifier = Modifier.tag(LabelTag), children = label)
- textField(Modifier.tag(TextFieldTag))
- },
- modifier = modifier
- ) { measurables, constraints, _ ->
- val placeholderPlaceable =
- measurables.find { it.tag == PlaceholderTag }?.measure(constraints)
- val baseLineOffset = FirstBaselineOffset.toIntPx()
+ val width = max(textfieldPlaceable.width, constraints.minWidth)
+ val height = max(
+ effectiveLabelBaseline + textfieldPlaceable.height + LastBaselineOffset.toIntPx(),
+ constraints.minHeight
+ )
- val labelConstraints = constraints
- .offset(vertical = -LastBaselineOffset.toIntPx())
- .copy(minWidth = 0, minHeight = 0)
- val labelPlaceable = measurables.first { it.tag == LabelTag }.measure(labelConstraints)
- val labelBaseline = labelPlaceable[LastBaseline] ?: labelPlaceable.height
- val labelEndPosition = (baseLineOffset - labelBaseline).coerceAtLeast(0)
- val effectiveLabelBaseline = max(labelBaseline, baseLineOffset)
-
- val textFieldConstraints = constraints
- .offset(vertical = -LastBaselineOffset.toIntPx() - effectiveLabelBaseline)
- .copy(minHeight = 0)
- val textfieldPlaceable = measurables
- .first { it.tag == TextFieldTag }
- .measure(textFieldConstraints)
- val textfieldLastBaseline = requireNotNull(textfieldPlaceable[LastBaseline]) {
- "No text last baseline."
- }
-
- val width = max(textfieldPlaceable.width, constraints.minWidth)
- val height = max(
- effectiveLabelBaseline + textfieldPlaceable.height + LastBaselineOffset.toIntPx(),
- constraints.minHeight
- )
-
- layout(width, height) {
- // Text field and label are placed with respect to the baseline offsets.
- // But if label is empty, then the text field should be centered vertically.
- if (labelPlaceable.width != 0) {
- // only respects the offset from the last baseline to the bottom of the text field
- val textfieldPositionY = height - LastBaselineOffset.toIntPx() -
- min(textfieldLastBaseline, textfieldPlaceable.height)
- placeLabelAndTextfield(
- width,
- height,
- textfieldPlaceable,
- labelPlaceable,
- placeholderPlaceable,
- labelEndPosition,
- textfieldPositionY,
- animationProgress
- )
- } else {
- placeTextfield(width, height, textfieldPlaceable, placeholderPlaceable)
+ layout(width, height) {
+ // Text field and label are placed with respect to the baseline offsets.
+ // But if label is empty, then the text field should be centered vertically.
+ if (labelPlaceable.width != 0) {
+ // only respects the offset from the last baseline to the bottom of the text field
+ val textfieldPositionY = height - LastBaselineOffset.toIntPx() -
+ min(textfieldLastBaseline, textfieldPlaceable.height)
+ placeLabelAndTextfield(
+ width,
+ height,
+ textfieldPlaceable,
+ labelPlaceable,
+ placeholderPlaceable,
+ labelEndPosition,
+ textfieldPositionY,
+ animationProgress
+ )
+ } else {
+ placeTextfield(width, height, textfieldPlaceable, placeholderPlaceable)
+ }
}
}
}
+
+ /**
+ * Layout of the leading and trailing icons and the text field part in [FilledTextField].
+ * It differs from the Row as it does not lose the minHeight constraint which is needed to
+ * correctly place the text field and label.
+ * Should be revisited if b/154202249 is fixed so that Row could be used instead
+ */
+ @Composable
+ fun IconsWithTextFieldLayout(
+ modifier: Modifier = Modifier,
+ textField: @Composable () -> Unit,
+ leading: @Composable (() -> Unit)?,
+ trailing: @Composable (() -> Unit)?,
+ leadingColor: Color,
+ trailingColor: Color
+ ) {
+ Layout(
+ children = {
+ if (leading != null) {
+ Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
+ Decoration(contentColor = leadingColor, children = leading)
+ }
+ }
+ if (trailing != null) {
+ Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
+ Decoration(contentColor = trailingColor, children = trailing)
+ }
+ }
+ textField()
+ },
+ modifier = modifier
+ ) { measurables, incomingConstraints, _ ->
+ val constraints =
+ incomingConstraints.copy(minWidth = 0, minHeight = 0)
+ var occupiedSpace = 0
+
+ val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
+ occupiedSpace += widthOrZero(leadingPlaceable)
+
+ val trailingPlaceable = measurables.find { it.tag == "trailing" }
+ ?.measure(constraints.offset(horizontal = -occupiedSpace))
+ occupiedSpace += widthOrZero(trailingPlaceable)
+
+ // represents the layout that holds textfield, label and placeholder
+ val textFieldPlaceable = measurables.first {
+ it.tag != "leading" && it.tag != "trailing"
+ }.measure(incomingConstraints.offset(horizontal = -occupiedSpace))
+ occupiedSpace += textFieldPlaceable.width
+
+ val width = max(occupiedSpace, incomingConstraints.minWidth)
+ val height = max(
+ heightOrZero(
+ listOf(
+ leadingPlaceable,
+ trailingPlaceable,
+ textFieldPlaceable
+ ).maxBy { heightOrZero(it) }
+ ),
+ incomingConstraints.minHeight
+ )
+ layout(width, height) {
+ leadingPlaceable?.place(
+ 0,
+ Alignment.CenterVertically.align(height - leadingPlaceable.height)
+ )
+ textFieldPlaceable.place(
+ leadingPlaceable?.width ?: 0,
+ Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+ )
+ trailingPlaceable?.place(
+ width - trailingPlaceable.width,
+ Alignment.CenterVertically.align(height - trailingPlaceable.height)
+ )
+ }
+ }
+ }
+
+ /**
+ * Places the provided text field, placeholder and label with respect to the baseline offsets in
+ * [FilledTextField]
+ */
+ private fun Placeable.PlacementScope.placeLabelAndTextfield(
+ width: Int,
+ height: Int,
+ textfieldPlaceable: Placeable,
+ labelPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ labelEndPosition: Int,
+ textPosition: Int,
+ animationProgress: Float
+ ) {
+ val labelCenterPosition = Alignment.CenterStart.align(
+ IntSize(
+ width - labelPlaceable.width,
+ height - labelPlaceable.height
+ )
+ )
+ val labelDistance = labelCenterPosition.y - labelEndPosition
+ val labelPositionY =
+ labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
+ labelPlaceable.place(0, labelPositionY)
+
+ textfieldPlaceable.place(0, textPosition)
+ placeholderPlaceable?.place(0, textPosition)
+ }
+
+ /**
+ * Places the provided text field and placeholder center vertically in [FilledTextField]
+ */
+ private fun Placeable.PlacementScope.placeTextfield(
+ width: Int,
+ height: Int,
+ textPlaceable: Placeable,
+ placeholderPlaceable: Placeable?
+ ) {
+ val textCenterPosition = Alignment.CenterStart.align(
+ IntSize(
+ width - textPlaceable.width,
+ height - textPlaceable.height
+ )
+ )
+ textPlaceable.place(0, textCenterPosition.y)
+ placeholderPlaceable?.place(0, textCenterPosition.y)
+ }
}
-/**
- * Layout of the leading and trailing icons and the text field.
- * It differs from the Row as it does not lose the minHeight constraint which is needed to
- * correctly place the text field and label.
- * Should be revisited if b/154202249 is fixed so that Row could be used instead
- */
-@Composable
-private fun IconsTextFieldLayout(
- modifier: Modifier = Modifier,
- textField: @Composable () -> Unit,
- leading: @Composable (() -> Unit)?,
- trailing: @Composable (() -> Unit)?,
- leadingColor: Color,
- trailingColor: Color
-) {
- Layout(
- children = {
- if (leading != null) {
- Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
- Decoration(contentColor = leadingColor, children = leading)
+private object OutlinedTextField {
+ /**
+ * Layout of the leading and trailing icons and the text field, label and placeholder in
+ * [OutlinedTextField].
+ * It doesn't use Row to position the icons and middle part because label should not be
+ * positioned in the middle part.
+ \ */
+ @Composable
+ fun IconsWithTextFieldLayout(
+ modifier: Modifier = Modifier,
+ textField: @Composable (Modifier) -> Unit,
+ placeholder: @Composable (() -> Unit)?,
+ label: @Composable () -> Unit,
+ leading: @Composable (() -> Unit)?,
+ trailing: @Composable (() -> Unit)?,
+ leadingColor: Color,
+ trailingColor: Color,
+ animationProgress: Float,
+ onLabelMeasured: (Int) -> Unit
+ ) {
+ Layout(
+ children = {
+ if (leading != null) {
+ Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
+ Decoration(contentColor = leadingColor, children = leading)
+ }
}
- }
- if (trailing != null) {
- Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
- Decoration(contentColor = trailingColor, children = trailing)
+ if (trailing != null) {
+ Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
+ Decoration(contentColor = trailingColor, children = trailing)
+ }
}
- }
- textField()
- },
- modifier = modifier
- ) { measurables, incomingConstraints, _ ->
- val constraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
- var occupiedSpace = 0
+ if (placeholder != null) {
+ Box(
+ modifier = Modifier
+ .tag(PlaceholderTag)
+ .padding(horizontal = TextFieldPadding),
+ children = placeholder
+ )
+ }
- val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
- occupiedSpace += leadingPlaceable?.width ?: 0
+ textField(
+ Modifier
+ .tag(TextFieldTag)
+ .padding(horizontal = TextFieldPadding)
+ )
- val trailingPlaceable = measurables.find { it.tag == "trailing" }
- ?.measure(constraints.offset(horizontal = -occupiedSpace))
- occupiedSpace += trailingPlaceable?.width ?: 0
+ Box(modifier = Modifier.tag(LabelTag), children = label)
+ },
+ modifier = modifier
+ ) { measurables, incomingConstraints, _ ->
+ // used to calculate the constraints for measuring elements that will be placed in a row
+ var occupiedSpaceHorizontally = 0
+ val bottomPadding = TextFieldPadding.toIntPx()
- val textFieldPlaceable = measurables.first {
- it.tag != "leading" && it.tag != "trailing"
- }.measure(incomingConstraints.offset(horizontal = -occupiedSpace))
- occupiedSpace += textFieldPlaceable.width
+ // measure leading icon
+ val constraints =
+ incomingConstraints.copy(minWidth = 0, minHeight = 0)
+ val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
+ occupiedSpaceHorizontally += widthOrZero(leadingPlaceable)
- val width = max(occupiedSpace, incomingConstraints.minWidth)
- val height = max(
- listOf(
+ // measure trailing icon
+ val trailingPlaceable = measurables.find { it.tag == "trailing" }
+ ?.measure(constraints.offset(horizontal = -occupiedSpaceHorizontally))
+ occupiedSpaceHorizontally += widthOrZero(trailingPlaceable)
+
+ // measure label
+ val labelConstraints = constraints.offset(
+ horizontal = -occupiedSpaceHorizontally,
+ vertical = -bottomPadding
+ )
+ val labelPlaceable =
+ measurables.first { it.tag == LabelTag }.measure(labelConstraints)
+ onLabelMeasured(labelPlaceable.width)
+
+ // measure text field
+ // on top we offset either by default padding or by label's half height if its too big
+ // minWidth must not be set to 0 due to how foundation TextField treats zero minWidth
+ val topPadding = max(labelPlaceable.height / 2, bottomPadding)
+ val textContraints = incomingConstraints.offset(
+ horizontal = -occupiedSpaceHorizontally,
+ vertical = -bottomPadding - topPadding
+ ).copy(minHeight = 0)
+ val textFieldPlaceable =
+ measurables.first { it.tag == TextFieldTag }.measure(textContraints)
+
+ // measure placeholder
+ val placeholderConstraints = textContraints.copy(minWidth = 0)
+ val placeholderPlaceable =
+ measurables.find { it.tag == PlaceholderTag }?.measure(placeholderConstraints)
+
+ val width = calculateWidth(
leadingPlaceable,
trailingPlaceable,
- textFieldPlaceable
- ).maxBy { it?.height ?: 0 }?.height ?: 0,
- incomingConstraints.minHeight
- )
- layout(width, height) {
- leadingPlaceable?.place(
- 0,
- Alignment.CenterVertically.align(height - leadingPlaceable.height)
+ textFieldPlaceable,
+ labelPlaceable,
+ placeholderPlaceable,
+ incomingConstraints
)
- textFieldPlaceable.place(
- leadingPlaceable?.width ?: 0,
- Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+ val height = calculateHeight(
+ leadingPlaceable,
+ trailingPlaceable,
+ textFieldPlaceable,
+ labelPlaceable,
+ placeholderPlaceable,
+ incomingConstraints,
+ density
)
- trailingPlaceable?.place(
- width - trailingPlaceable.width,
- Alignment.CenterVertically.align(height - trailingPlaceable.height)
- )
+ layout(width, height) {
+ place(
+ height,
+ width,
+ leadingPlaceable,
+ trailingPlaceable,
+ textFieldPlaceable,
+ labelPlaceable,
+ placeholderPlaceable,
+ animationProgress,
+ density
+ )
+ }
}
}
+
+ /**
+ * Calculate the width of the [OutlinedTextField] given all elements that should be
+ * placed inside
+ */
+ private fun calculateWidth(
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ textFieldPlaceable: Placeable,
+ labelPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ constraints: Constraints
+ ): Int {
+ val middleSection = widthOrZero(
+ listOf(
+ textFieldPlaceable,
+ labelPlaceable,
+ placeholderPlaceable
+ ).maxBy { widthOrZero(it) }
+ )
+ val wrappedWidth =
+ widthOrZero(leadingPlaceable) + middleSection + widthOrZero(trailingPlaceable)
+ return max(wrappedWidth, constraints.minWidth)
+ }
+
+ /**
+ * Calculate the height of the [OutlinedTextField] given all elements that should be
+ * placed inside
+ */
+ private fun calculateHeight(
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ textFieldPlaceable: Placeable,
+ labelPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ constraints: Constraints,
+ density: Float
+ ): Int {
+ // middle section is defined as a height of the text field or placeholder ( whichever is
+ // taller) plus 16.dp or half height of the label if it is taller, given that the label
+ // is vertically centered to the top edge of the resulting text field's container
+ val inputFieldHeight = max(textFieldPlaceable.height, heightOrZero(placeholderPlaceable))
+ val topBottomPadding = TextFieldPadding.value * density
+ val middleSectionHeight = inputFieldHeight + topBottomPadding + max(
+ topBottomPadding,
+ labelPlaceable.height / 2f
+ )
+ return max(
+ listOf(
+ heightOrZero(leadingPlaceable),
+ heightOrZero(trailingPlaceable),
+ middleSectionHeight.roundToInt()
+ ).max() ?: 0,
+ constraints.minHeight
+ )
+ }
+
+ /**
+ * Places the provided text field, placeholder, label, optional leading and trailing icons inside
+ * the [OutlinedTextField]
+ */
+ private fun Placeable.PlacementScope.place(
+ height: Int,
+ width: Int,
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ textFieldPlaceable: Placeable,
+ labelPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ animationProgress: Float,
+ density: Float
+ ) {
+ // placed center vertically and to the start edge horizontally
+ leadingPlaceable?.place(
+ 0,
+ Alignment.CenterVertically.align(height - leadingPlaceable.height)
+ )
+
+ // placed center vertically and to the end edge horizontally
+ trailingPlaceable?.place(
+ width - trailingPlaceable.width,
+ Alignment.CenterVertically.align(height - trailingPlaceable.height)
+ )
+
+ // if animation progress is 0, the label will be centered vertically
+ // if animation progress is 1, vertically it will be centered to the container's top edge
+ // horizontally it is placed after the leading icon
+ val labelPositionY =
+ Alignment.CenterVertically.align(height - labelPlaceable.height) * (1 -
+ animationProgress) - (labelPlaceable.height / 2) * animationProgress
+ val labelPositionX = (TextFieldPadding.value * density) +
+ widthOrZero(leadingPlaceable) * (1 - animationProgress)
+ labelPlaceable.place(labelPositionX.roundToInt(), labelPositionY.roundToInt())
+
+ // placed center vertically and after the leading icon horizontally
+ textFieldPlaceable.place(
+ widthOrZero(leadingPlaceable),
+ Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+ )
+
+ // placed center vertically and after the leading icon horizontally
+ placeholderPlaceable?.place(
+ widthOrZero(leadingPlaceable),
+ Alignment.CenterVertically.align(height - placeholderPlaceable.height)
+ )
+ }
}
+private val Placeable.nonZero: Boolean get() = this.width != 0 || this.height != 0
+private fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
+private fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
+
+/**
+ * A modifier that applies padding only if the size of the element is not zero
+ */
private fun Modifier.iconPadding(start: Dp = 0.dp, end: Dp = 0.dp) =
this + object : LayoutModifier {
override fun MeasureScope.measure(
@@ -828,10 +1457,8 @@
}
}
-private val Placeable.nonZero: Boolean get() = this.width != 0 || this.height != 0
-
/**
- * A draw modifier that draws a bottom indicator line
+ * A draw modifier that draws a bottom indicator line in [FilledTextField]
*/
private fun Modifier.drawIndicatorLine(lineWidth: Dp, color: Color): Modifier {
return drawBehind {
@@ -847,50 +1474,75 @@
}
/**
- * Places the provided text field, placeholder and label with respect to the baseline offsets
+ * A draw modifier to draw a border line in [OutlinedTextField]
*/
-private fun Placeable.PlacementScope.placeLabelAndTextfield(
- width: Int,
- height: Int,
- textfieldPlaceable: Placeable,
- labelPlaceable: Placeable,
- placeholderPlaceable: Placeable?,
- labelEndPosition: Int,
- textPosition: Int,
- animationProgress: Float
-) {
- val labelCenterPosition = Alignment.CenterStart.align(
- IntSize(
- width - labelPlaceable.width,
- height - labelPlaceable.height
- )
- )
- val labelDistance = labelCenterPosition.y - labelEndPosition
- val labelPositionY =
- labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
- labelPlaceable.place(0, labelPositionY)
+private fun Modifier.drawOutlinedBorder(
+ borderParams: OutlinedBorderParams
+): Modifier = drawBehind {
+ val padding = TextFieldPadding.value * density
+ val innerPadding = OutlinedTextFieldInnerPadding.value * density
- textfieldPlaceable.place(0, textPosition)
- placeholderPlaceable?.place(0, textPosition)
+ val lineWidth = borderParams.borderWidth.value.value * density
+ val width: Float = size.width
+ val height: Float = size.height
+
+ val radius = borderParams.cornerRadius.value * density
+ val dx = if (radius > width / 2) width / 2 else radius
+ val dy = if (radius > height / 2) height / 2 else radius
+
+ val path = Path().apply {
+ // width and height minus corners and line width
+ val effectiveWidth: Float = width - 2 * dx - lineWidth
+ val effectiveHeight: Float = height - 2 * dy - lineWidth
+
+ // top-right corner
+ moveTo(width - lineWidth / 2, dy + lineWidth / 2)
+ relativeQuadraticBezierTo(0f, -dy, -dx, -dy)
+
+ // top line with gap
+ val diff = borderParams.labelWidth.value
+ if (diff == 0f) {
+ relativeLineTo(-effectiveWidth, 0f)
+ } else {
+ val effectivePadding = padding - innerPadding - dx - lineWidth / 2
+ val gap = diff + 2 * innerPadding
+ relativeLineTo(-effectiveWidth + effectivePadding + gap, 0f)
+ relativeMoveTo(-gap, 0f)
+ relativeLineTo(-effectivePadding, 0f)
+ }
+
+ // top-left corner and left line
+ relativeQuadraticBezierTo(-dx, 0f, -dx, dy)
+ relativeLineTo(0f, effectiveHeight)
+
+ // bottom-left corner and bottom line
+ relativeQuadraticBezierTo(0f, dy, dx, dy)
+ relativeLineTo(effectiveWidth, 0f)
+
+ // bottom-right corner and right line
+ relativeQuadraticBezierTo(dx, 0f, dx, -dy)
+ relativeLineTo(0f, -effectiveHeight)
+ }
+
+ drawPath(
+ path = path,
+ color = borderParams.color.value,
+ style = Stroke(width = lineWidth)
+ )
}
/**
- * Places the provided text field and placeholder center vertically
+ * A data class that stores parameters needed for [drawOutlinedBorder] modifier
*/
-private fun Placeable.PlacementScope.placeTextfield(
- width: Int,
- height: Int,
- textPlaceable: Placeable,
- placeholderPlaceable: Placeable?
+@Stable
+private class OutlinedBorderParams(
+ initialBorderWidth: Dp,
+ initialColor: Color
) {
- val textCenterPosition = Alignment.CenterStart.align(
- IntSize(
- width - textPlaceable.width,
- height - textPlaceable.height
- )
- )
- textPlaceable.place(0, textCenterPosition.y)
- placeholderPlaceable?.place(0, textCenterPosition.y)
+ val borderWidth = mutableStateOf(initialBorderWidth)
+ val color = mutableStateOf(initialColor)
+ val cornerRadius = OutlinedTextFieldCornerRadius
+ val labelWidth = mutableStateOf(0f)
}
private object TextFieldTransitionScope {
@@ -1016,5 +1668,16 @@
private val TextFieldMinWidth = 280.dp
private val FirstBaselineOffset = 20.dp
private val LastBaselineOffset = 16.dp
-private val HorizontalTextFieldPadding = 16.dp
-private val HorizontalIconPadding = 12.dp
\ No newline at end of file
+private val TextFieldPadding = 16.dp
+private val HorizontalIconPadding = 12.dp
+private val OutlinedTextFieldInnerPadding = 4.dp
+
+// TODO(b/158077409) support shape in OutlinedTextField
+private val OutlinedTextFieldCornerRadius = 4.dp
+
+/*
+This padding is used to allow label not overlap with the content above it. This 8.dp will work
+for default cases when developers do not override the label's font size. If they do, they will
+need to add additional padding themselves
+*/
+private val OutlinedTextFieldTopPadding = 8.dp
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
index 9225fd0..a96308c 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
@@ -20,7 +20,8 @@
import androidx.ui.core.Modifier
import androidx.ui.core.gesture.doubleTapGestureFilter
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
@@ -65,11 +66,8 @@
@get:Rule
val composeTestRule = createComposeRule(disableTransitions = true)
- private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
- private val eventPeriod get() = dispatcherRule.eventPeriod
-
@get:Rule
- val inputDispatcherRule: TestRule = dispatcherRule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recordedDoubleClicks = mutableListOf<Offset>()
private val expectedClickPosition =
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
index 5d8638d..e28dac9 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
@@ -21,7 +21,7 @@
import androidx.ui.geometry.Offset
import androidx.ui.layout.Stack
import androidx.ui.layout.fillMaxSize
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
import androidx.ui.test.createComposeRule
import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
@@ -49,7 +49,6 @@
@get:Rule
val composeTestRule = createComposeRule(disableTransitions = true)
- private val eventPeriod = AndroidInputDispatcher.TestRule().eventPeriod
private val recorder = MultiPointerInputRecorder()
@Test
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
index ed63f5f..2739718 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
@@ -23,7 +23,7 @@
import androidx.ui.layout.Stack
import androidx.ui.layout.fillMaxSize
import androidx.ui.layout.wrapContentSize
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
@@ -121,7 +121,7 @@
val composeTestRule = createComposeRule(disableTransitions = true)
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(
disableDispatchInRealTime = true,
eventPeriodOverride = eventPeriod
)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
index bcd8bb5..38e8ad6 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
@@ -31,16 +31,24 @@
assertThat(getCurrentPosition(pointerId)).isEqualTo(position)
}
-internal fun AndroidInputDispatcher.sendUpAndCheck(pointerId: Int) {
- sendUp(pointerId)
+internal fun AndroidInputDispatcher.sendUpAndCheck(pointerId: Int, delay: Long? = null) {
+ if (delay != null) {
+ sendUp(pointerId, delay)
+ } else {
+ sendUp(pointerId)
+ }
assertThat(getCurrentPosition(pointerId)).isNull()
}
-internal fun AndroidInputDispatcher.sendCancelAndCheck() {
- sendCancel()
+internal fun AndroidInputDispatcher.sendCancelAndCheck(delay: Long? = null) {
+ if (delay != null) {
+ sendCancel(delay)
+ } else {
+ sendCancel()
+ }
verifyNoGestureInProgress()
}
internal fun InputDispatcher.verifyNoGestureInProgress() {
- assertThat(getState().partialGesture).isNull()
+ assertThat(isGestureInProgress).isFalse()
}
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
index d4b5038..6f22944b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
import androidx.ui.test.InputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.unit.Duration
@@ -85,9 +86,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
- disableDispatchInRealTime = true
- )
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt
new file mode 100644
index 0000000..3197ace
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.ui.test.inputdispatcher
+
+import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+
+class IsGestureInProgressTest {
+ companion object {
+ private val anyPosition = Offset.Zero
+ }
+
+ @get:Rule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
+
+ private val subject = AndroidInputDispatcher {}
+
+ @Test
+ fun downUp() {
+ assertThat(subject.isGestureInProgress).isFalse()
+ subject.sendDown(1, anyPosition)
+ assertThat(subject.isGestureInProgress).isTrue()
+ subject.sendUp(1)
+ assertThat(subject.isGestureInProgress).isFalse()
+ }
+
+ @Test
+ fun downCancel() {
+ assertThat(subject.isGestureInProgress).isFalse()
+ subject.sendDown(1, anyPosition)
+ assertThat(subject.isGestureInProgress).isTrue()
+ subject.sendCancel()
+ assertThat(subject.isGestureInProgress).isFalse()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
index c7a6aba..7fd7f34 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
@@ -21,6 +21,8 @@
import android.view.MotionEvent.ACTION_POINTER_DOWN
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -48,11 +50,8 @@
private val position2_1 = Offset(21f, 21f)
}
- private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
- private val eventPeriod get() = dispatcherRule.eventPeriod
-
@get:Rule
- val inputDispatcherRule: TestRule = dispatcherRule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -62,8 +61,8 @@
recorder.disposeEvents()
}
- private fun AndroidInputDispatcher.sendCancelAndCheckPointers() {
- sendCancelAndCheck()
+ private fun AndroidInputDispatcher.sendCancelAndCheckPointers(delay: Long? = null) {
+ sendCancelAndCheck(delay)
assertThat(getCurrentPosition(pointer1)).isNull()
assertThat(getCurrentPosition(pointer2)).isNull()
}
@@ -88,6 +87,25 @@
}
@Test
+ fun onePointerWithDelay() {
+ subject.sendDownAndCheck(pointer1, position1_1)
+ subject.sendCancelAndCheckPointers(2 * eventPeriod)
+ subject.verifyNoGestureInProgress()
+ recorder.assertHasValidEventTimes()
+
+ recorder.events.apply {
+ var t = 0L
+ assertThat(this).hasSize(2)
+ this[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+ this[0].verifyPointer(pointer1, position1_1)
+
+ t += 2 * eventPeriod
+ this[1].verifyEvent(1, ACTION_CANCEL, 0, t)
+ this[1].verifyPointer(pointer1, position1_1)
+ }
+ }
+
+ @Test
fun multiplePointers() {
subject.sendDownAndCheck(pointer1, position1_1)
subject.sendDownAndCheck(pointer2, position2_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
index 1ba9bfa..acb3ec0 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
@@ -19,6 +19,8 @@
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -54,11 +56,8 @@
}
}
- private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
- private val eventPeriod get() = dispatcherRule.eventPeriod
-
@get:Rule
- val inputDispatcherRule: TestRule = dispatcherRule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val position = Offset(config.x, config.y)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
index 81b0ea2..edd89c4 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
@@ -22,6 +22,8 @@
import android.view.MotionEvent.ACTION_POINTER_UP
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -56,11 +58,8 @@
private val position1_2 = Offset(12f, 12f)
}
- private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
- private val eventPeriod get() = dispatcherRule.eventPeriod
-
@get:Rule
- val inputDispatcherRule: TestRule = dispatcherRule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
index ccfe985..fd31d50 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
@@ -23,6 +23,8 @@
import android.view.MotionEvent.ACTION_POINTER_UP
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -57,11 +59,8 @@
private val position1_3 = Offset(13f, 13f)
}
- private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
- private val eventPeriod get() = dispatcherRule.eventPeriod
-
@get:Rule
- val inputDispatcherRule: TestRule = dispatcherRule
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -96,6 +95,23 @@
}
@Test
+ fun onePointerWithDelay() {
+ subject.sendDownAndCheck(pointer1, position1_1)
+ subject.movePointerAndCheck(pointer1, position1_2)
+ subject.sendMove(2 * eventPeriod)
+
+ var t = 0L
+ recorder.assertHasValidEventTimes()
+ assertThat(recorder.events).hasSize(2)
+ recorder.events[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+ recorder.events[0].verifyPointer(pointer1, position1_1)
+
+ t += 2 * eventPeriod
+ recorder.events[1].verifyEvent(1, ACTION_MOVE, 0, t) // pointer1
+ recorder.events[1].verifyPointer(pointer1, position1_2)
+ }
+
+ @Test
fun twoPointers_downDownMoveMove() {
// 2 fingers, both go down before they move
subject.sendDownAndCheck(pointer1, position1_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
index 0e83446..44bda8b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
@@ -19,6 +19,7 @@
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -64,7 +65,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(
disableDispatchInRealTime = true,
eventPeriodOverride = config.eventPeriod
)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
index b69b16a..9ea938f 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
@@ -21,6 +21,7 @@
import android.view.MotionEvent.ACTION_UP
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -94,9 +95,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
- disableDispatchInRealTime = true
- )
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
index 3b2432c..02b3298 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
@@ -20,6 +20,7 @@
import android.view.MotionEvent.ACTION_UP
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -92,7 +93,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(
disableDispatchInRealTime = true,
eventPeriodOverride = config.eventPeriod
)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
index 13379bc..13db791 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
@@ -20,6 +20,7 @@
import android.view.MotionEvent.ACTION_MOVE
import android.view.MotionEvent.ACTION_UP
import androidx.test.filters.SmallTest
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -84,9 +85,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
- disableDispatchInRealTime = true
- )
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
index 8872c65..67ebc5b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
@@ -22,6 +22,8 @@
import android.view.MotionEvent.ACTION_UP
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.android.AndroidInputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import androidx.ui.test.util.assertHasValidEventTimes
@@ -50,8 +52,7 @@
}
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MotionEventRecorder()
private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -81,6 +82,26 @@
}
@Test
+ fun onePointerWithDelay() {
+ subject.sendDownAndCheck(pointer1, position1_1)
+ subject.sendUpAndCheck(pointer1, 2 * eventPeriod)
+ subject.verifyNoGestureInProgress()
+
+ recorder.assertHasValidEventTimes()
+ recorder.events.apply {
+ var t = 0L
+ assertThat(this).hasSize(2)
+
+ this[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+ this[0].verifyPointer(pointer1, position1_1)
+
+ t += 2 * eventPeriod
+ this[1].verifyEvent(1, ACTION_UP, 0, t) // pointer1
+ this[1].verifyPointer(pointer1, position1_1)
+ }
+ }
+
+ @Test
fun multiplePointers_ascending() {
subject.sendDownAndCheck(pointer1, position1_1)
subject.sendDownAndCheck(pointer2, position2_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
index 939eca6..388bd24 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -50,8 +50,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MultiPointerInputRecorder()
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
index dccbf8f..9e2d7a0 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.partialgesturescope.Common.partialGesture
import androidx.ui.test.runOnIdleCompose
@@ -49,8 +49,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MultiPointerInputRecorder()
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
index 22cce9e..8fcfe89 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.movePointerBy
import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -57,8 +57,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MultiPointerInputRecorder()
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
index 35c49a0..bd7e890 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.partialgesturescope.Common.partialGesture
import androidx.ui.test.sendCancel
@@ -45,8 +45,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
@Before
fun setUp() {
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
index 5eb9c1f..599bfa3 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.movePointerTo
import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -57,8 +57,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MultiPointerInputRecorder()
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
index 925914d..fc6da04 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -52,8 +52,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val inputDispatcherRule: TestRule =
- AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+ val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
private val recorder = MultiPointerInputRecorder()
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
index be87be9..4de2e6f 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
@@ -22,6 +22,7 @@
import androidx.ui.core.semantics.SemanticsNode
import androidx.ui.geometry.Offset
import androidx.ui.geometry.lerp
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
import androidx.ui.unit.Duration
import androidx.ui.unit.IntSize
import androidx.ui.unit.PxBounds
@@ -29,6 +30,7 @@
import androidx.ui.unit.milliseconds
import androidx.ui.util.lerp
import kotlin.math.atan2
+import kotlin.math.ceil
import kotlin.math.cos
import kotlin.math.roundToInt
import kotlin.math.sign
@@ -67,7 +69,7 @@
}
internal fun dispose() {
- InputDispatcher.saveState(owner, inputDispatcher)
+ inputDispatcher.saveState(owner)
_semanticsNode = null
_inputDispatcher = null
}
@@ -358,10 +360,13 @@
require(endVelocity >= 0f) {
"Velocity cannot be $endVelocity, it must be positive"
}
- // TODO(b/146551983): require that duration >= 2.5 * eventPeriod
- // TODO(b/146551983): check that eventPeriod < 40 milliseconds
- require(duration >= 25.milliseconds) {
- "Duration must be at least 25ms because velocity requires at least 3 input events"
+ require(eventPeriod < 40.milliseconds.inMilliseconds()) {
+ "InputDispatcher.eventPeriod must be smaller than 40ms in order to generate velocities"
+ }
+ val minimumDuration = ceil(2.5f * eventPeriod).roundToInt()
+ require(duration >= minimumDuration.milliseconds) {
+ "Duration must be at least ${minimumDuration}ms because " +
+ "velocity requires at least 3 input events"
}
val globalStart = localToGlobal(start)
val globalEnd = localToGlobal(end)
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
index 5d1d32c..e66e165 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
@@ -25,7 +25,13 @@
import androidx.ui.test.android.AndroidOwnerRegistry
import androidx.ui.unit.Duration
import androidx.ui.unit.inMilliseconds
+import androidx.ui.unit.milliseconds
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
import java.util.WeakHashMap
+import kotlin.math.max
+import kotlin.math.roundToInt
/**
* Interface for dispatching full and partial gestures.
@@ -45,8 +51,33 @@
* Chaining methods:
* * [delay]
*/
-internal interface InputDispatcher {
+internal abstract class InputDispatcher {
companion object : AndroidOwnerRegistry.OnRegistrationChangedListener {
+ /**
+ * Whether or not events with an eventTime in the future should be dispatched at that
+ * exact eventTime. If `true`, will sleep until the eventTime, if `false`, will send the
+ * event immediately without blocking.
+ */
+ private var dispatchInRealTime: Boolean = true
+
+ /**
+ * The minimum time between two successive injected MotionEvents, 10 milliseconds.
+ * Ideally, the value should reflect a realistic pointer input sample rate, but that
+ * depends on too many factors. Instead, the value is chosen comfortably below the
+ * targeted frame rate (60 fps, equating to a 16ms period).
+ */
+ var eventPeriod = 10.milliseconds.inMilliseconds()
+ private set
+
+ /**
+ * Indicates that [nextDownTime] is not set
+ */
+ private const val DownTimeNotSet = -1L
+
+ /**
+ * Stores the [InputDispatcherState] of each [Owner]. The state will be restored in an
+ * [InputDispatcher] when it is created for an owner that has a state stored.
+ */
private val states = WeakHashMap<Owner, InputDispatcherState>()
init {
@@ -61,18 +92,15 @@
val view = owner.view
return AndroidInputDispatcher { view.dispatchTouchEvent(it) }.apply {
states.remove(owner)?.also {
- restoreState(it)
+ // TODO(b/157653315): Move restore state to constructor
+ if (it.partialGesture != null) {
+ nextDownTime = it.nextDownTime
+ partialGesture = it.partialGesture
+ }
}
}
}
- internal fun saveState(owner: Owner?, inputDispatcher: InputDispatcher) {
- // Owner may have been removed already
- if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
- states[owner] = inputDispatcher.getState()
- }
- }
-
override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
if (!registered) {
states.remove(owner)
@@ -80,6 +108,96 @@
}
}
+ internal fun saveState(owner: Owner?) {
+ if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
+ states[owner] = InputDispatcherState(nextDownTime, partialGesture)
+ }
+ }
+
+ protected var nextDownTime = DownTimeNotSet
+ protected var partialGesture: PartialGesture? = null
+
+ /**
+ * Indicates if a gesture is in progress or not. A gesture is in progress if at least one
+ * finger is (still) touching the screen.
+ */
+ val isGestureInProgress: Boolean
+ get() = partialGesture != null
+
+ /**
+ * The current time, in the time scale used by gesture events.
+ */
+ protected abstract val now: Long
+
+ /**
+ * Generates the downTime of the next gesture with the given [duration]. The gesture's
+ * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
+ * after the next one, it will start exactly [duration] after the start of the next gesture.
+ * Always use this method to determine the downTime of the [down event][sendDown] of a gesture.
+ *
+ * If the duration is unknown when calling this method, use a duration of zero and update
+ * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
+ * incrementally if the gesture unfolds gradually.
+ */
+ private fun generateDownTime(duration: Duration): Long {
+ val downTime = if (nextDownTime == DownTimeNotSet) {
+ now
+ } else {
+ nextDownTime
+ }
+ nextDownTime = downTime + duration.inMilliseconds()
+ return downTime
+ }
+
+ /**
+ * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
+ * any event time from the current gesture. Use this when the expected duration passed to
+ * [generateDownTime] has changed.
+ */
+ private fun moveNextDownTime(duration: Duration) {
+ generateDownTime(duration)
+ }
+
+ /**
+ * Increases the eventTime with the given [time]. Also pushes the downTime for the next
+ * chained gesture by the same amount to facilitate chaining.
+ */
+ private fun PartialGesture.increaseEventTime(time: Long = eventPeriod) {
+ moveNextDownTime(time.milliseconds)
+ lastEventTime += time
+ }
+
+ /**
+ * Delays the next gesture by the given [duration], but does not block. Guarantees that the
+ * first event time of the next gesture will be exactly [duration] later then if that gesture
+ * would be injected without this delay, provided that the next gesture is started using the
+ * same [InputDispatcher] instance as the one used to end the last gesture.
+ *
+ * Note: this does not affect the time of the next event for the _current_ partial gesture,
+ * using [sendMove], [sendUp] and [sendCancel], but it will affect the time of the _next_
+ * gesture (including partial gestures started with [sendDown]).
+ *
+ * @param duration The duration of the delay. Must be positive
+ */
+ fun delay(duration: Duration) {
+ require(duration >= Duration.Zero) {
+ "duration of a delay can only be positive, not $duration"
+ }
+ moveNextDownTime(duration)
+ }
+
+ /**
+ * Blocks until uptime of [time], if [dispatchInRealTime] is `true`.
+ */
+ protected fun sleepUntil(time: Long) {
+ if (dispatchInRealTime) {
+ val currTime = now
+ if (currTime < time) {
+ Thread.sleep(time - currTime)
+ }
+ }
+ }
+
/**
* Sends a click event at [position]. There will be 10ms in between the down and the up
* event. This method blocks until all input events have been dispatched.
@@ -143,21 +261,79 @@
curves: List<(Long) -> Offset>,
duration: Duration,
keyTimes: List<Long> = emptyList()
- )
+ ) {
+ val startTime = 0L
+ val endTime = duration.inMilliseconds()
+
+ // Validate input
+ require(duration >= 1.milliseconds) {
+ "duration must be at least 1 millisecond, not $duration"
+ }
+ val validRange = startTime..endTime
+ require(keyTimes.all { it in validRange }) {
+ "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
+ }
+ require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
+ "keyTimes must be sorted: $keyTimes"
+ }
+
+ // Send down events
+ curves.forEachIndexed { i, curve ->
+ sendDown(i, curve(startTime))
+ }
+
+ // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
+ var currTime = startTime
+ var key = 0
+ while (currTime < endTime) {
+ // advance key
+ while (key < keyTimes.size && keyTimes[key] <= currTime) {
+ key++
+ }
+ // send events between t and next keyTime
+ val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
+ sendPartialSwipes(curves, currTime, tNext)
+ currTime = tNext
+ }
+
+ // And end with up events
+ repeat(curves.size) {
+ sendUp(it)
+ }
+ }
/**
- * Blocks for the given [duration] in order to delay the next gesture. Guarantees that the
- * first event time of the next gesture will be exactly [duration] later then if that gesture
- * would be injected without this delay, provided that the next gesture is started using the
- * same [InputDispatcher] instance as the one used to end the last gesture.
+ * Sends move events between `f([t0])` and `f([tN])` during the time window `(downTime + t0,
+ * downTime + tN]`, using [fs] to sample the coordinate of each event. The number of events
+ * sent (#numEvents) is such that the time between each event is as close to [eventPeriod] as
+ * possible, but at least 1. The first event is sent at time `downTime + (tN - t0) /
+ * #numEvents`, the last event is sent at time tN.
*
- * Note: this does not affect the time of the next event for the _current_ partial gesture,
- * using [sendMove], [sendUp] and [sendCancel], but it will affect the time of the _next_
- * gesture (including partial gestures started with [sendDown]).
- *
- * @param duration The duration of the delay. Must be positive
+ * @param fs The functions that define the coordinates of the respective gestures over time
+ * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
+ * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
*/
- fun delay(duration: Duration)
+ private fun sendPartialSwipes(
+ fs: List<(Long) -> Offset>,
+ t0: Long,
+ tN: Long
+ ) {
+ var step = 0
+ // How many steps will we take between t0 and tN? At least 1, and a number that will
+ // bring as as close to eventPeriod as possible
+ val steps = max(1, ((tN - t0) / eventPeriod.toFloat()).roundToInt())
+
+ var tPrev = t0
+ while (step++ < steps) {
+ val progress = step / steps.toFloat()
+ val t = androidx.ui.util.lerp(t0, tN, progress)
+ fs.forEachIndexed { i, f ->
+ movePointer(i, f(t))
+ }
+ sendMove(t - tPrev)
+ tPrev = t
+ }
+ }
/**
* During a partial gesture, returns the position of the last touch event of the given
@@ -167,7 +343,9 @@
* @return The current position of the pointer with the given [pointerId], or `null` if the
* pointer is not currently in use
*/
- fun getCurrentPosition(pointerId: Int): Offset?
+ fun getCurrentPosition(pointerId: Int): Offset? {
+ return partialGesture?.lastPositions?.get(pointerId)
+ }
/**
* Sends a down event at [position] for the pointer with the given [pointerId], starting a
@@ -202,7 +380,27 @@
* @see sendUp
* @see sendCancel
*/
- fun sendDown(pointerId: Int, position: Offset)
+ fun sendDown(pointerId: Int, position: Offset) {
+ var gesture = partialGesture
+
+ // Check if this pointer is not already down
+ require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
+ }
+
+ gesture?.flushPointerUpdates()
+
+ // Start a new gesture, or add the pointerId to the existing gesture
+ if (gesture == null) {
+ gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
+ partialGesture = gesture
+ } else {
+ gesture.lastPositions.put(pointerId, position)
+ }
+
+ // Send the DOWN event
+ gesture.sendDown(pointerId)
+ }
/**
* Updates the position of the pointer with the given [pointerId] to the given [position],
@@ -221,56 +419,176 @@
* @see sendUp
* @see sendCancel
*/
- fun movePointer(pointerId: Int, position: Offset)
+ fun movePointer(pointerId: Int, position: Offset) {
+ val gesture = partialGesture
+
+ // Check if this pointer is in the gesture
+ check(gesture != null) {
+ "Cannot move pointers, no gesture is in progress"
+ }
+ require(gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot move pointer $pointerId, it is not active in the current gesture"
+ }
+
+ gesture.lastPositions.put(pointerId, position)
+ gesture.hasPointerUpdates = true
+ }
/**
- * Sends a move event 10 milliseconds after the previous injected event of this gesture,
- * without moving any of the pointers. Use this to commit all changes in pointer location
- * made with [movePointer]. The sent event will contain the current position of all pointers.
- * See [sendDown] for more information on how to make complete gestures from partial gestures.
+ * Sends a move event [delay] milliseconds after the previous injected event of this gesture,
+ * without moving any of the pointers. The default [delay] is [10][eventPeriod] milliseconds.
+ * Use this to commit all changes in pointer location made with [movePointer]. The sent event
+ * will contain the current position of all pointers. See [sendDown] for more information on
+ * how to make complete gestures from partial gestures.
+ *
+ * @param delay The time in milliseconds between the previously injected event and the move
+ * event. [10][eventPeriod] milliseconds by default.
*/
- fun sendMove()
+ fun sendMove(delay: Long = eventPeriod) {
+ val gesture = checkNotNull(partialGesture) {
+ "Cannot send MOVE event, no gesture is in progress"
+ }
+ require(delay >= 0) {
+ "Cannot send MOVE event with a delay of $delay ms"
+ }
+
+ gesture.increaseEventTime(delay)
+ gesture.sendMove()
+ gesture.hasPointerUpdates = false
+ }
/**
- * Sends an up event for the given [pointerId] at the current position of that pointer, 10
- * milliseconds after the previous injected event of this gesture. This method blocks until
- * the input event has been dispatched. See [sendDown] for more information on how to make
- * complete gestures from partial gestures.
+ * Sends an up event for the given [pointerId] at the current position of that pointer,
+ * [delay] milliseconds after the previous injected event of this gesture. The default
+ * [delay] is 0 milliseconds. This method blocks until the input event has been dispatched.
+ * See [sendDown] for more information on how to make complete gestures from partial gestures.
*
* @param pointerId The id of the pointer to lift up, as supplied in [sendDown]
+ * @param delay The time in milliseconds between the previously injected event and the move
+ * event. 0 milliseconds by default.
*
* @see sendDown
* @see movePointer
* @see sendMove
* @see sendCancel
*/
- fun sendUp(pointerId: Int)
+ fun sendUp(pointerId: Int, delay: Long = 0) {
+ val gesture = partialGesture
+
+ // Check if this pointer is in the gesture
+ check(gesture != null) {
+ "Cannot send UP event, no gesture is in progress"
+ }
+ require(gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
+ }
+ require(delay >= 0) {
+ "Cannot send UP event with a delay of $delay ms"
+ }
+
+ gesture.flushPointerUpdates()
+ gesture.increaseEventTime(delay)
+
+ // First send the UP event
+ gesture.sendUp(pointerId)
+
+ // Then remove the pointer, and end the gesture if no pointers are left
+ gesture.lastPositions.remove(pointerId)
+ if (gesture.lastPositions.isEmpty) {
+ partialGesture = null
+ }
+ }
/**
- * Sends a cancel event 10 milliseconds after the previous injected event of this gesture.
- * This method blocks until the input event has been dispatched. See [sendDown] for more
- * information on how to make complete gestures from partial gestures.
+ * Sends a cancel event [delay] milliseconds after the previous injected event of this
+ * gesture. The default [delay] is [10][eventPeriod] milliseconds. This method blocks until
+ * the input event has been dispatched. See [sendDown] for more information on how to make
+ * complete gestures from partial gestures.
+ *
+ * @param delay The time in milliseconds between the previously injected event and the cancel
+ * event. [10][eventPeriod] milliseconds by default.
*
* @see sendDown
* @see movePointer
* @see sendMove
* @see sendUp
*/
- fun sendCancel()
+ fun sendCancel(delay: Long = eventPeriod) {
+ val gesture = checkNotNull(partialGesture) {
+ "Cannot send CANCEL event, no gesture is in progress"
+ }
+ require(delay >= 0) {
+ "Cannot send CANCEL event with a delay of $delay ms"
+ }
+
+ gesture.increaseEventTime(delay)
+ gesture.sendCancel()
+ partialGesture = null
+ }
/**
- * Returns the state of this input dispatcher, in case a partial gesture is in progress.
+ * Sends a MOVE event with all pointer locations, if any of the pointers has been moved by
+ * [movePointer] since the last MOVE event.
*/
- fun getState(): InputDispatcherState
+ private fun PartialGesture.flushPointerUpdates() {
+ if (hasPointerUpdates) {
+ sendMove(eventPeriod)
+ }
+ }
+
+ protected abstract fun PartialGesture.sendDown(pointerId: Int)
+
+ protected abstract fun PartialGesture.sendMove()
+
+ protected abstract fun PartialGesture.sendUp(pointerId: Int)
+
+ protected abstract fun PartialGesture.sendCancel()
/**
- * Restores the state of this input dispatcher, in case a partial gesture was in progress. If
- * a partial gesture was not in progress, no state is restored.
+ * A test rule that modifies [InputDispatcher]s behavior. Can be used to disable
+ * dispatching of MotionEvents in real time (skips the sleep before injection of an event) or
+ * to change the time between consecutive injected events.
*
- * @param state The state to restore
+ * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
+ * in the future will be dispatched as soon as possible or at that exact eventTime. If
+ * `false` or not set, will sleep until the eventTime, if `true`, will send the event
+ * immediately without blocking. See also [dispatchInRealTime].
+ * @param eventPeriodOverride If set, specifies a different period in milliseconds between
+ * two consecutive injected motion events injected by this [InputDispatcher]. If not
+ * set, the event period of 10 milliseconds is unchanged.
+ *
+ * @see InputDispatcher.eventPeriod
*/
- fun restoreState(state: InputDispatcherState)
- // TODO(b/157653315): Move restore state to constructor
+ internal class InputDispatcherTestRule(
+ private val disableDispatchInRealTime: Boolean = false,
+ private val eventPeriodOverride: Long? = null
+ ) : TestRule {
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return ModifyingStatement(base)
+ }
+
+ inner class ModifyingStatement(private val base: Statement) : Statement() {
+ override fun evaluate() {
+ if (disableDispatchInRealTime) {
+ dispatchInRealTime = false
+ }
+ if (eventPeriodOverride != null) {
+ InputDispatcher.eventPeriod = eventPeriodOverride
+ }
+ try {
+ base.evaluate()
+ } finally {
+ if (disableDispatchInRealTime) {
+ dispatchInRealTime = true
+ }
+ if (eventPeriodOverride != null) {
+ InputDispatcher.eventPeriod = 10L
+ }
+ }
+ }
+ }
+ }
}
/**
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
index 556b1ac..5f01b4b 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
@@ -29,215 +29,38 @@
import android.view.MotionEvent.ACTION_UP
import androidx.ui.geometry.Offset
import androidx.ui.test.InputDispatcher
-import androidx.ui.test.InputDispatcherState
import androidx.ui.test.PartialGesture
-import androidx.ui.unit.Duration
-import androidx.ui.unit.inMilliseconds
-import androidx.ui.unit.milliseconds
-import androidx.ui.util.lerp
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
-import kotlin.math.max
-import kotlin.math.roundToInt
internal class AndroidInputDispatcher(
private val sendEvent: (MotionEvent) -> Unit
-) : InputDispatcher {
- companion object {
- /**
- * Whether or not events with an eventTime in the future should be dispatched at that
- * exact eventTime. If `true`, will sleep until the eventTime, if `false`, will send the
- * event immediately without blocking.
- */
- private var dispatchInRealTime: Boolean = true
-
- /**
- * The minimum time between two successive injected MotionEvents, 10 milliseconds.
- * Ideally, the value should reflect a realistic pointer input sample rate, but that
- * depends on too many factors. Instead, the value is chosen comfortably below the
- * targeted frame rate (60 fps, equating to a 16ms period).
- */
- private var eventPeriod = 10.milliseconds.inMilliseconds()
-
- /**
- * Indicates that [nextDownTime] is not set
- */
- private const val DownTimeNotSet = -1L
- }
+) : InputDispatcher() {
private val handler = Handler(Looper.getMainLooper())
- private var nextDownTime = DownTimeNotSet
- private var partialGesture: PartialGesture? = null
- override fun getState(): InputDispatcherState {
- return InputDispatcherState(nextDownTime, partialGesture)
- }
+ override val now: Long get() = SystemClock.uptimeMillis()
- override fun restoreState(state: InputDispatcherState) {
- if (state.partialGesture != null) {
- nextDownTime = state.nextDownTime
- partialGesture = state.partialGesture
- }
- }
-
- /**
- * Generates the downTime of the next gesture with the given [duration]. The gesture's
- * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
- * after the next one, it will start exactly [duration] after the start of the next gesture.
- * Always use this method to determine the downTime of the [ACTION_DOWN] event of a gesture.
- *
- * If the duration is unknown when calling this method, use a duration of zero and update
- * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
- * incrementally if the gesture unfolds gradually.
- */
- private fun generateDownTime(duration: Duration): Long {
- val downTime = if (nextDownTime == DownTimeNotSet) {
- SystemClock.uptimeMillis()
- } else {
- nextDownTime
- }
- nextDownTime = downTime + duration.inMilliseconds()
- return downTime
- }
-
- /**
- * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
- * any event time from the current gesture. Use this when the expected duration passed to
- * [generateDownTime] has changed.
- */
- private fun moveNextDownTime(duration: Duration) {
- generateDownTime(duration)
- }
-
- override fun delay(duration: Duration) {
- require(duration >= Duration.Zero) {
- "duration of a delay can only be positive, not $duration"
- }
- moveNextDownTime(duration)
- sleepUntil(nextDownTime)
- }
-
- override fun getCurrentPosition(pointerId: Int): Offset? {
- return partialGesture?.lastPositions?.get(pointerId)
- }
-
- override fun sendDown(pointerId: Int, position: Offset) {
- var gesture = partialGesture
-
- // Check if this pointer is not already down
- require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
- "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
- }
-
- gesture?.flushPointerUpdates()
-
- // Start a new gesture, or add the pointerId to the existing gesture
- if (gesture == null) {
- gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
- partialGesture = gesture
- } else {
- gesture.lastPositions.put(pointerId, position)
- }
-
- // Send the ACTION_DOWN or ACTION_POINTER_DOWN
- val positions = gesture.lastPositions
- gesture.sendMotionEvent(
- if (positions.size() == 1) ACTION_DOWN else ACTION_POINTER_DOWN,
- positions.indexOfKey(pointerId)
+ override fun PartialGesture.sendDown(pointerId: Int) {
+ sendMotionEvent(
+ if (lastPositions.size() == 1) ACTION_DOWN else ACTION_POINTER_DOWN,
+ lastPositions.indexOfKey(pointerId)
)
}
- // Move 1 pointer and don't send a move event
- override fun movePointer(pointerId: Int, position: Offset) {
- val gesture = partialGesture
-
- // Check if this pointer is in the gesture
- check(gesture != null) {
- "Cannot move pointers, no gesture is in progress"
- }
- require(gesture.lastPositions.containsKey(pointerId)) {
- "Cannot move pointer $pointerId, it is not active in the current gesture"
- }
-
- gesture.lastPositions.put(pointerId, position)
- gesture.hasPointerUpdates = true
+ override fun PartialGesture.sendMove() {
+ sendMotionEvent(ACTION_MOVE, 0)
}
- // Move 0 pointers and send a move event
- override fun sendMove() {
- sendMove(eventPeriod)
- }
-
- /**
- * Sends a move event, [deltaTime] milliseconds after the last event
- */
- // TODO(b/157717418): make this public API
- private fun sendMove(deltaTime: Long) {
- val gesture = checkNotNull(partialGesture) {
- "Cannot send MOVE event, no gesture is in progress"
- }
-
- gesture.increaseEventTime(deltaTime)
- gesture.sendMotionEvent(ACTION_MOVE, 0)
- gesture.hasPointerUpdates = false
- }
-
- override fun sendUp(pointerId: Int) {
- val gesture = partialGesture
-
- // Check if this pointer is in the gesture
- check(gesture != null) {
- "Cannot send UP event, no gesture is in progress"
- }
- require(gesture.lastPositions.containsKey(pointerId)) {
- "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
- }
-
- gesture.flushPointerUpdates()
-
- // First send the ACTION_UP or ACTION_POINTER_UP
- val positions = gesture.lastPositions
- gesture.sendMotionEvent(
- if (positions.size() == 1) ACTION_UP else ACTION_POINTER_UP,
- positions.indexOfKey(pointerId)
+ override fun PartialGesture.sendUp(pointerId: Int) {
+ sendMotionEvent(
+ if (lastPositions.size() == 1) ACTION_UP else ACTION_POINTER_UP,
+ lastPositions.indexOfKey(pointerId)
)
-
- // Then remove the pointer, and end the gesture if no pointers are left
- positions.remove(pointerId)
- if (positions.isEmpty) {
- partialGesture = null
- }
}
- override fun sendCancel() {
- val gesture = checkNotNull(partialGesture) {
- "Cannot send CANCEL event, no gesture is in progress"
- }
-
- gesture.increaseEventTime()
- gesture.sendMotionEvent(ACTION_CANCEL, 0)
- partialGesture = null
- }
-
- /**
- * Increases the eventTime with the given [time]. Also pushes the downTime for the next
- * chained gesture by the same amount to facilitate chaining.
- */
- private fun PartialGesture.increaseEventTime(time: Long = eventPeriod) {
- moveNextDownTime(time.milliseconds)
- lastEventTime += time
- }
-
- /**
- * Sends a MOVE event with all pointer locations, if any of the pointers has been moved by
- * [movePointer] since the last MOVE event.
- */
- private fun PartialGesture.flushPointerUpdates() {
- if (hasPointerUpdates) {
- sendMove()
- }
+ override fun PartialGesture.sendCancel() {
+ sendMotionEvent(ACTION_CANCEL, 0)
}
/**
@@ -258,86 +81,8 @@
)
}
- override fun sendSwipes(
- curves: List<(Long) -> Offset>,
- duration: Duration,
- keyTimes: List<Long>
- ) {
- val startTime = 0L
- val endTime = duration.inMilliseconds()
-
- // Validate input
- require(duration >= 1.milliseconds) {
- "duration must be at least 1 millisecond, not $duration"
- }
- val validRange = startTime..endTime
- require(keyTimes.all { it in validRange }) {
- "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
- }
- require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
- "keyTimes must be sorted: $keyTimes"
- }
-
- // Send down events
- curves.forEachIndexed { i, curve ->
- sendDown(i, curve(startTime))
- }
-
- // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
- var currTime = startTime
- var key = 0
- while (currTime < endTime) {
- // advance key
- while (key < keyTimes.size && keyTimes[key] <= currTime) {
- key++
- }
- // send events between t and next keyTime
- val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
- sendPartialSwipes(curves, currTime, tNext)
- currTime = tNext
- }
-
- // And end with up events
- repeat(curves.size) {
- sendUp(it)
- }
- }
-
/**
- * Sends move events between `f([t0])` and `f([tN])` during the time window `(downTime + t0,
- * downTime + tN]`, using [fs] to sample the coordinate of each event. The number of events
- * sent (#numEvents) is such that the time between each event is as close to [eventPeriod] as
- * possible, but at least 1. The first event is sent at time `downTime + (tN - t0) /
- * #numEvents`, the last event is sent at time tN.
- *
- * @param fs The functions that define the coordinates of the respective gestures over time
- * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
- * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
- */
- private fun sendPartialSwipes(
- fs: List<(Long) -> Offset>,
- t0: Long,
- tN: Long
- ) {
- var step = 0
- // How many steps will we take between t0 and tN? At least 1, and a number that will
- // bring as as close to eventPeriod as possible
- val steps = max(1, ((tN - t0) / eventPeriod.toFloat()).roundToInt())
-
- var tPrev = t0
- while (step++ < steps) {
- val progress = step / steps.toFloat()
- val t = lerp(t0, tN, progress)
- fs.forEachIndexed { i, f ->
- movePointer(i, f(t))
- }
- sendMove(t - tPrev)
- tPrev = t
- }
- }
-
- /**
- * Sends an event with the given parameters. Method blocks if [dispatchInRealTime] is `true`.
+ * Sends an event with the given parameters.
*/
private fun sendMotionEvent(
downTime: Long,
@@ -375,15 +120,6 @@
)
}
- private fun sleepUntil(time: Long) {
- if (dispatchInRealTime) {
- val currTime = SystemClock.uptimeMillis()
- if (currTime < time) {
- SystemClock.sleep(time - currTime)
- }
- }
- }
-
/**
* Sends the [event] to the MotionEvent dispatcher and [recycles][MotionEvent.recycle] it
* regardless of the result. This method blocks until the event is sent.
@@ -404,52 +140,4 @@
latch.await()
}
}
-
- /**
- * A test rule that modifies [AndroidInputDispatcher]s behavior. Can be used to disable
- * dispatching of MotionEvents in real time (skips the sleep before injection of an event) or
- * to change the time between consecutive injected events.
- *
- * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
- * in the future will be dispatched as soon as possible or at that exact eventTime. If
- * `false` or not set, will sleep until the eventTime, if `true`, will send the event
- * immediately without blocking. See also [dispatchInRealTime].
- * @param eventPeriodOverride If set, specifies a different period in milliseconds between
- * two consecutive injected motion events injected by this [AndroidInputDispatcher]. If not
- * set, the event period of 10 milliseconds is unchanged.
- *
- * @see AndroidInputDispatcher.eventPeriod
- */
- internal class TestRule(
- private val disableDispatchInRealTime: Boolean = false,
- private val eventPeriodOverride: Long? = null
- ) : org.junit.rules.TestRule {
-
- val eventPeriod get() = AndroidInputDispatcher.eventPeriod
-
- override fun apply(base: Statement, description: Description?): Statement {
- return ModifyingStatement(base)
- }
-
- inner class ModifyingStatement(private val base: Statement) : Statement() {
- override fun evaluate() {
- if (disableDispatchInRealTime) {
- dispatchInRealTime = false
- }
- if (eventPeriodOverride != null) {
- AndroidInputDispatcher.eventPeriod = eventPeriodOverride
- }
- try {
- base.evaluate()
- } finally {
- if (disableDispatchInRealTime) {
- dispatchInRealTime = true
- }
- if (eventPeriodOverride != null) {
- AndroidInputDispatcher.eventPeriod = 10L
- }
- }
- }
- }
- }
}
diff --git a/ui/ui-text-core/api/0.1.0-dev14.txt b/ui/ui-text-core/api/0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/0.1.0-dev14.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/api/current.txt b/ui/ui-text-core/api/current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/current.txt
+++ b/ui/ui-text-core/api/current.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/api/public_plus_experimental_current.txt b/ui/ui-text-core/api/public_plus_experimental_current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_current.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/api/restricted_current.txt b/ui/ui-text-core/api/restricted_current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/restricted_current.txt
+++ b/ui/ui-text-core/api/restricted_current.txt
@@ -653,6 +653,7 @@
}
public final class TextRangeKt {
+ method public static androidx.ui.text.TextRange TextRange(int index);
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
diff --git a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
index 6558120..22c0541 100644
--- a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
+++ b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
@@ -40,7 +40,7 @@
@Test
fun test_delete() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
BackspaceKeyEditOp().process(eb)
@@ -51,7 +51,7 @@
@Test
fun test_delete_from_offset0() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
BackspaceKeyEditOp().process(eb)
@@ -73,7 +73,7 @@
@Test
fun test_delete_with_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
eb.setComposition(2, 3)
BackspaceKeyEditOp().process(eb)
@@ -85,7 +85,7 @@
@Test
fun test_delete_surrogate_pair() {
- val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2, 2))
+ val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
BackspaceKeyEditOp().process(eb)
@@ -107,7 +107,7 @@
@Test
fun test_delete_with_composition_surrogate_pair() {
- val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2, 2))
+ val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
eb.setComposition(4, 6)
BackspaceKeyEditOp().process(eb)
@@ -122,7 +122,7 @@
fun test_delete_with_composition_zwj_emoji() {
val eb = EditingBuffer(
"$ZWJ_EMOJI$ZWJ_EMOJI",
- TextRange(ZWJ_EMOJI.length, ZWJ_EMOJI.length))
+ TextRange(ZWJ_EMOJI.length))
BackspaceKeyEditOp().process(eb)
diff --git a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
index 5918079..4e38697 100644
--- a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
+++ b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
@@ -39,7 +39,7 @@
@Test
fun test_left() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
MoveCursorEditOp(-1).process(eb)
@@ -50,7 +50,7 @@
@Test
fun test_left_multiple() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
MoveCursorEditOp(-2).process(eb)
@@ -61,7 +61,7 @@
@Test
fun test_left_from_offset0() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
MoveCursorEditOp(-1).process(eb)
@@ -72,7 +72,7 @@
@Test
fun test_right() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
MoveCursorEditOp(1).process(eb)
@@ -83,7 +83,7 @@
@Test
fun test_right_multiple() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
MoveCursorEditOp(2).process(eb)
@@ -94,7 +94,7 @@
@Test
fun test_right_from_offset_length() {
- val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+ val eb = EditingBuffer("ABCDE", TextRange(5))
MoveCursorEditOp(1).process(eb)
@@ -105,7 +105,7 @@
@Test
fun test_left_surrogate_pair() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
MoveCursorEditOp(-1).process(eb)
@@ -116,7 +116,7 @@
@Test
fun test_left_multiple_surrogate_pair() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
MoveCursorEditOp(-2).process(eb)
@@ -127,7 +127,7 @@
@Test
fun test_right_surrogate_pair() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
MoveCursorEditOp(1).process(eb)
@@ -138,7 +138,7 @@
@Test
fun test_right_multiple_surrogate_pair() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
MoveCursorEditOp(2).process(eb)
@@ -150,7 +150,7 @@
@Test
@SdkSuppress(minSdkVersion = 26)
fun test_left_emoji() {
- val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length, FAMILY.length))
+ val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
MoveCursorEditOp(-1).process(eb)
@@ -162,7 +162,7 @@
@Test
@SdkSuppress(minSdkVersion = 26)
fun test_right_emoji() {
- val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length, FAMILY.length))
+ val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
MoveCursorEditOp(1).process(eb)
diff --git a/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt b/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
index 5db1374..e9d837f 100644
--- a/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
+++ b/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
@@ -91,11 +91,15 @@
typeface = createTypeface(style, typefaceAdapter)
}
- style.localeList?.let {
+ if (style.localeList != null && style.localeList != LocaleList.current) {
if (Build.VERSION.SDK_INT >= 24) {
- textLocales = it.toAndroidLocaleList()
+ textLocales = style.localeList.toAndroidLocaleList()
} else {
- val locale = if (it.isEmpty()) Locale.current else it[0]
+ val locale = if (style.localeList.isEmpty()) {
+ Locale.current
+ } else {
+ style.localeList[0]
+ }
textLocale = locale.toJavaLocale()
}
}
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
index 5823dd6..a93b13d 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
@@ -35,7 +35,7 @@
// The editing buffer used for applying editor commands from IME.
private var mBuffer: EditingBuffer =
- EditingBuffer(initialText = "", initialSelection = TextRange(0, 0))
+ EditingBuffer(initialText = "", initialSelection = TextRange(0))
/**
* Must be called whenever new editor model arrives.
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
index ec37636..a643659 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
@@ -47,7 +47,7 @@
* A selection range visible to IME.
* The selection range must be valid range in the given text.
*/
- val selection: TextRange = TextRange(0, 0),
+ val selection: TextRange = TextRange(0),
/**
* A composition range visible to IME.
@@ -94,7 +94,7 @@
@Stable
val text: String = "",
@Stable
- val selection: TextRange = TextRange(0, 0),
+ val selection: TextRange = TextRange(0),
@Stable
val composition: TextRange? = null
) {
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
index 4fa69a7..98241c88 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
@@ -61,3 +61,8 @@
*/
operator fun contains(offset: Int): Boolean = offset in min until max
}
+
+/**
+ * Creates a [TextRange] where start is equal to end, and the value of those are [index].
+ */
+fun TextRange(index: Int): TextRange = TextRange(start = index, end = index)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
index c75b551..662d88f 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
@@ -30,7 +30,7 @@
@Test
fun test_insert_empty() {
- val eb = EditingBuffer("", TextRange(0, 0))
+ val eb = EditingBuffer("", TextRange(0))
CommitTextEditOp("X", 1).process(eb)
@@ -41,7 +41,7 @@
@Test
fun test_insert_cursor_tail() {
- val eb = EditingBuffer("A", TextRange(1, 1))
+ val eb = EditingBuffer("A", TextRange(1))
CommitTextEditOp("X", 1).process(eb)
@@ -52,7 +52,7 @@
@Test
fun test_insert_cursor_head() {
- val eb = EditingBuffer("A", TextRange(1, 1))
+ val eb = EditingBuffer("A", TextRange(1))
CommitTextEditOp("X", 0).process(eb)
@@ -63,7 +63,7 @@
@Test
fun test_insert_cursor_far_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
CommitTextEditOp("X", 2).process(eb)
@@ -74,7 +74,7 @@
@Test
fun test_insert_cursor_far_head() {
- val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+ val eb = EditingBuffer("ABCDE", TextRange(4))
CommitTextEditOp("X", -2).process(eb)
@@ -85,7 +85,7 @@
@Test
fun test_insert_empty_text_cursor_head() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
CommitTextEditOp("", 0).process(eb)
@@ -96,7 +96,7 @@
@Test
fun test_insert_empty_text_cursor_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
CommitTextEditOp("", 1).process(eb)
@@ -107,7 +107,7 @@
@Test
fun test_insert_empty_text_cursor_far_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
CommitTextEditOp("", 2).process(eb)
@@ -118,7 +118,7 @@
@Test
fun test_insert_empty_text_cursor_far_head() {
- val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+ val eb = EditingBuffer("ABCDE", TextRange(4))
CommitTextEditOp("", -2).process(eb)
@@ -129,7 +129,7 @@
@Test
fun test_cancel_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 4) // Mark "BCD" as composition
CommitTextEditOp("X", 1).process(eb)
@@ -166,7 +166,7 @@
@Test
fun test_cursor_position_too_small() {
- val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+ val eb = EditingBuffer("ABCDE", TextRange(5))
CommitTextEditOp("X", -1000).process(eb)
@@ -177,7 +177,7 @@
@Test
fun test_cursor_position_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+ val eb = EditingBuffer("ABCDE", TextRange(5))
CommitTextEditOp("X", 1000).process(eb)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
index e6be59e..40840aa 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
@@ -30,7 +30,7 @@
@Test
fun test_delete_after() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
DeleteSurroundingTextEditOp(0, 1).process(eb)
@@ -41,7 +41,7 @@
@Test
fun test_delete_before() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
DeleteSurroundingTextEditOp(1, 0).process(eb)
@@ -52,7 +52,7 @@
@Test
fun test_delete_both() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(1, 1).process(eb)
@@ -63,7 +63,7 @@
@Test
fun test_delete_after_multiple() {
- val eb = EditingBuffer("ABCDE", TextRange(2, 2))
+ val eb = EditingBuffer("ABCDE", TextRange(2))
DeleteSurroundingTextEditOp(0, 2).process(eb)
@@ -74,7 +74,7 @@
@Test
fun test_delete_before_multiple() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(2, 0).process(eb)
@@ -85,7 +85,7 @@
@Test
fun test_delete_both_multiple() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(2, 2).process(eb)
@@ -108,7 +108,7 @@
@Test
fun test_delete_before_too_many() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(1000, 0).process(eb)
@@ -119,7 +119,7 @@
@Test
fun test_delete_after_too_many() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(0, 1000).process(eb)
@@ -130,7 +130,7 @@
@Test
fun test_delete_both_too_many() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
DeleteSurroundingTextEditOp(1000, 1000).process(eb)
@@ -141,7 +141,7 @@
@Test
fun test_delete_composition_no_intersection_preceding_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(0, 1)
@@ -155,7 +155,7 @@
@Test
fun test_delete_composition_no_intersection_trailing_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(4, 5)
@@ -169,7 +169,7 @@
@Test
fun test_delete_composition_intersection_preceding_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(0, 3)
@@ -183,7 +183,7 @@
@Test
fun test_delete_composition_intersection_trailing_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(3, 5)
@@ -197,7 +197,7 @@
@Test
fun test_delete_covered_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(2, 3)
@@ -210,7 +210,7 @@
@Test
fun test_delete_composition_covered() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.setComposition(0, 5)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
index 908bfb2..1699039a 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
@@ -35,7 +35,7 @@
@Test
fun test_delete_after() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2, 2))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
DeleteSurroundingTextInCodePointsEditOp(0, 1).process(eb)
@@ -46,7 +46,7 @@
@Test
fun test_delete_before() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2, 2))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
DeleteSurroundingTextInCodePointsEditOp(1, 0).process(eb)
@@ -57,7 +57,7 @@
@Test
fun test_delete_both() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(1, 1).process(eb)
@@ -68,7 +68,7 @@
@Test
fun test_delete_after_multiple() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4, 4))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4))
DeleteSurroundingTextInCodePointsEditOp(0, 2).process(eb)
@@ -79,7 +79,7 @@
@Test
fun test_delete_before_multiple() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(2, 0).process(eb)
@@ -90,7 +90,7 @@
@Test
fun test_delete_both_multiple() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(2, 2).process(eb)
@@ -113,7 +113,7 @@
@Test
fun test_delete_before_too_many() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(1000, 0).process(eb)
@@ -124,7 +124,7 @@
@Test
fun test_delete_after_too_many() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(0, 1000).process(eb)
@@ -135,7 +135,7 @@
@Test
fun test_delete_both_too_many() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
DeleteSurroundingTextInCodePointsEditOp(1000, 1000).process(eb)
@@ -146,7 +146,7 @@
@Test
fun test_delete_composition_no_intersection_preceding_composition() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(0, 2)
@@ -160,7 +160,7 @@
@Test
fun test_delete_composition_no_intersection_trailing_composition() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(8, 10)
@@ -174,7 +174,7 @@
@Test
fun test_delete_composition_intersection_preceding_composition() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(0, 6)
@@ -188,7 +188,7 @@
@Test
fun test_delete_composition_intersection_trailing_composition() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(6, 10)
@@ -202,7 +202,7 @@
@Test
fun test_delete_covered_composition() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(4, 6)
@@ -215,7 +215,7 @@
@Test
fun test_delete_composition_covered() {
- val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+ val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
eb.setComposition(0, 10)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
index b79530c..dc669f0 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
@@ -41,7 +41,7 @@
val tis: TextInputService = mock()
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
- val model = TextFieldValue("ABCDE", TextRange(0, 0))
+ val model = TextFieldValue("ABCDE", TextRange(0))
proc.onNewState(model, tis, dummyInputSessionToken)
assertEquals(model, proc.mPreviousState)
val captor = argumentCaptor<TextFieldValue>()
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
index f4a16ba..80e9955 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
@@ -39,7 +39,7 @@
@Test
fun test_insert() {
- val eb = EditingBuffer("", TextRange(0, 0))
+ val eb = EditingBuffer("", TextRange(0))
eb.replace(0, 0, "A")
@@ -74,7 +74,7 @@
@Test
fun test_delete() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.replace(0, 1, "")
@@ -149,7 +149,7 @@
@Test
fun test_setCompostion_and_cancelComposition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(0, 5) // Make all text as composition
assertStrWithChars("ABCDE", eb)
@@ -190,7 +190,7 @@
@Test
fun test_setCompostion_and_commitComposition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(0, 5) // Make all text as composition
assertStrWithChars("ABCDE", eb)
@@ -231,7 +231,7 @@
@Test
fun test_setCursor_and_get_cursor() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.cursor = 1
assertStrWithChars("ABCDE", eb)
@@ -263,7 +263,7 @@
@Test
fun test_delete_preceding_cursor_no_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.delete(1, 2)
assertStrWithChars("ACDE", eb)
@@ -273,7 +273,7 @@
@Test
fun test_delete_trailing_cursor_no_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+ val eb = EditingBuffer("ABCDE", TextRange(3))
eb.delete(1, 2)
assertStrWithChars("ACDE", eb)
@@ -305,7 +305,7 @@
@Test
fun test_delete_preceding_composition_no_intersection() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 2)
eb.delete(2, 3)
@@ -318,7 +318,7 @@
@Test
fun test_delete_trailing_composition_no_intersection() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(3, 4)
eb.delete(2, 3)
@@ -331,7 +331,7 @@
@Test
fun test_delete_preceding_composition_intersection() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 3)
eb.delete(2, 4)
@@ -344,7 +344,7 @@
@Test
fun test_delete_trailing_composition_intersection() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(3, 5)
eb.delete(2, 4)
@@ -357,7 +357,7 @@
@Test
fun test_delete_composition_contains_delrange() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(2, 5)
eb.delete(3, 4)
@@ -370,7 +370,7 @@
@Test
fun test_delete_delrange_contains_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(3, 4)
eb.delete(2, 5)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
index 1ef1d40..4e69ff3 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
@@ -30,7 +30,7 @@
@Test
fun test_set() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 4)
FinishComposingTextEditOp().process(eb)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
index ccbc49f..63781b0 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
@@ -31,7 +31,7 @@
@Test
fun test_set() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(1, 4).process(eb)
@@ -44,7 +44,7 @@
@Test
fun test_preserve_ongoing_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 3)
@@ -73,7 +73,7 @@
@Test
fun test_set_reversed() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(4, 1).process(eb)
@@ -86,7 +86,7 @@
@Test
fun test_set_too_small() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(-1000, -1000).process(eb)
@@ -97,7 +97,7 @@
@Test
fun test_set_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(1000, 1000).process(eb)
@@ -108,7 +108,7 @@
@Test
fun test_set_too_small_and_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(-1000, 1000).process(eb)
@@ -121,7 +121,7 @@
@Test
fun test_set_too_small_and_too_large_reversed() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetComposingRegionEditOp(1000, -1000).process(eb)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
index d56f3bff..b88cada 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
@@ -31,7 +31,7 @@
@Test
fun test_insert_empty() {
- val eb = EditingBuffer("", TextRange(0, 0))
+ val eb = EditingBuffer("", TextRange(0))
SetComposingTextEditOp("X", 1).process(eb)
@@ -44,7 +44,7 @@
@Test
fun test_insert_cursor_tail() {
- val eb = EditingBuffer("A", TextRange(1, 1))
+ val eb = EditingBuffer("A", TextRange(1))
SetComposingTextEditOp("X", 1).process(eb)
@@ -57,7 +57,7 @@
@Test
fun test_insert_cursor_head() {
- val eb = EditingBuffer("A", TextRange(1, 1))
+ val eb = EditingBuffer("A", TextRange(1))
SetComposingTextEditOp("X", 0).process(eb)
@@ -70,7 +70,7 @@
@Test
fun test_insert_cursor_far_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
SetComposingTextEditOp("X", 2).process(eb)
@@ -83,7 +83,7 @@
@Test
fun test_insert_cursor_far_head() {
- val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+ val eb = EditingBuffer("ABCDE", TextRange(4))
SetComposingTextEditOp("X", -2).process(eb)
@@ -96,7 +96,7 @@
@Test
fun test_insert_empty_text_cursor_head() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
SetComposingTextEditOp("", 0).process(eb)
@@ -107,7 +107,7 @@
@Test
fun test_insert_empty_text_cursor_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
SetComposingTextEditOp("", 1).process(eb)
@@ -118,7 +118,7 @@
@Test
fun test_insert_empty_text_cursor_far_tail() {
- val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+ val eb = EditingBuffer("ABCDE", TextRange(1))
SetComposingTextEditOp("", 2).process(eb)
@@ -129,7 +129,7 @@
@Test
fun test_insert_empty_text_cursor_far_head() {
- val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+ val eb = EditingBuffer("ABCDE", TextRange(4))
SetComposingTextEditOp("", -2).process(eb)
@@ -140,7 +140,7 @@
@Test
fun test_cancel_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 4) // Mark "BCD" as composition
SetComposingTextEditOp("X", 1).process(eb)
@@ -183,7 +183,7 @@
@Test
fun test_cursor_position_too_small() {
- val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+ val eb = EditingBuffer("ABCDE", TextRange(5))
SetComposingTextEditOp("X", -1000).process(eb)
@@ -196,7 +196,7 @@
@Test
fun test_cursor_position_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+ val eb = EditingBuffer("ABCDE", TextRange(5))
SetComposingTextEditOp("X", 1000).process(eb)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
index 7456f0b..a338de6 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
@@ -31,7 +31,7 @@
@Test
fun test_set() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(1, 4).process(eb)
@@ -43,7 +43,7 @@
@Test
fun test_preserve_ongoing_composition() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
eb.setComposition(1, 3)
@@ -71,7 +71,7 @@
@Test
fun test_set_reversed() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(4, 1).process(eb)
@@ -83,7 +83,7 @@
@Test
fun test_set_too_small() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(-1000, -1000).process(eb)
@@ -94,7 +94,7 @@
@Test
fun test_set_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(1000, 1000).process(eb)
@@ -105,7 +105,7 @@
@Test
fun test_set_too_small_too_large() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(-1000, 1000).process(eb)
@@ -117,7 +117,7 @@
@Test
fun test_set_too_small_too_large_reversed() {
- val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+ val eb = EditingBuffer("ABCDE", TextRange(0))
SetSelectionEditOp(1000, -1000).process(eb)
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
index 7d30150..688bec7 100644
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
@@ -93,7 +93,7 @@
@Test
fun test_on_edit_command() {
val ops = listOf(CommitTextEditOp("Hello, World", 1))
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
whenever(processor.onEditCommands(ops)).thenReturn(dummyEditorState)
@@ -111,7 +111,7 @@
fun test_on_release() {
val position = Offset(100f, 200f)
val offset = 10
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
whenever(textLayoutResult.getOffsetForPosition(position)).thenReturn(offset)
@@ -165,7 +165,7 @@
@Test
fun on_focus() {
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
TextFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
KeyboardType.Text, ImeAction.Unspecified, onValueChange, onEditorActionPerformed)
verify(textInputService).startInput(
@@ -230,7 +230,7 @@
whenever(textLayoutResult.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = Offset(5f, 6f)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
@@ -247,7 +247,7 @@
@Test
fun notify_focused_rect_without_focus() {
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
@@ -268,7 +268,7 @@
whenever(textLayoutResult.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = Offset(5f, 6f)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(12, 12))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(12))
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
@@ -337,7 +337,7 @@
fun check_on_release_uses_offset_map() {
val position = Offset(100f, 200f)
val offset = 10
- val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
whenever(textLayoutResult.getOffsetForPosition(position)).thenReturn(offset)
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
index b865592..d0a3cd1 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
@@ -199,19 +199,12 @@
function =
"androidx.ui.tooling.inspector.LayoutInspectorTreeTest\$buildTree\$1\$1\$1\$1.invoke",
left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
- children = listOf("Button")
- )
- validate(
- name = "Button",
- fileName = "Button.kt",
- function = "androidx.ui.material.ButtonKt.Button",
- left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
children = listOf("Surface")
)
validate(
name = "Surface",
- fileName = "Button.kt",
- function = "androidx.ui.material.ButtonKt\$Button\$1.invoke",
+ fileName = "Surface.kt",
+ function = "androidx.ui.material.SurfaceKt.Surface",
left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
children = listOf("SurfaceLayout")
)
@@ -232,7 +225,7 @@
validate(
name = "Box",
fileName = "Button.kt",
- function = "androidx.ui.material.ButtonKt\$Button\$1\$1.invoke",
+ function = "androidx.ui.material.ButtonKt\$Button\$1.invoke",
left = 16.0.dp, top = 26.9.dp, width = 32.0.dp, height = 20.0.dp,
children = listOf("Column")
)
@@ -260,7 +253,7 @@
validate(
name = "ProvideTextStyle",
fileName = "Button.kt",
- function = "androidx.ui.material.ButtonKt\$Button\$1\$1\$1.invoke",
+ function = "androidx.ui.material.ButtonKt\$Button\$1\$1.invoke",
left = 21.8.dp, top = 27.6.dp, width = 20.4.dp, height = 18.9.dp,
children = listOf("Text")
)
diff --git a/vectordrawable/vectordrawable-animated/api/api_lint.ignore b/vectordrawable/vectordrawable-animated/api/api_lint.ignore
index ffd0fdf..8bbfad4 100644
--- a/vectordrawable/vectordrawable-animated/api/api_lint.ignore
+++ b/vectordrawable/vectordrawable-animated/api/api_lint.ignore
@@ -59,3 +59,7 @@
Missing nullability on parameter `dr` in method `unregisterAnimationCallback`
MissingNullability: androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat#unregisterAnimationCallback(android.graphics.drawable.Drawable, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback) parameter #1:
Missing nullability on parameter `callback` in method `unregisterAnimationCallback`
+
+
+NotCloseable: androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat:
+ Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat