diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle index 9e3d5dbc0714b..343582d064268 100644 --- a/activity/activity-compose/build.gradle +++ b/activity/activity-compose/build.gradle @@ -76,7 +76,6 @@ androidx { type = SoftwareType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS inceptionYear = "2020" description = "Compose integration with Activity" - legacyDisableKotlinStrictApiMode = true samples(project(":activity:activity-compose:activity-compose-samples")) } diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/LocalActivity.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/LocalActivity.kt index a96dcd3ce00fb..a93c85f03f26c 100644 --- a/activity/activity-compose/src/main/java/androidx/activity/compose/LocalActivity.kt +++ b/activity/activity-compose/src/main/java/androidx/activity/compose/LocalActivity.kt @@ -17,18 +17,21 @@ package androidx.activity.compose import android.app.Activity +import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalWithComputedDefaultOf import androidx.compose.ui.platform.LocalContext /** - * Provides the [android.app.Activity] belonging to the current [LocalContext]. + * Provides the [Activity] belonging to the current [LocalContext]. * * Note, when possible you should always prefer using the finer grained composition locals where * available. This API should be used as a fallback when the required API is only available via - * [android.app.Activity]. + * [Activity]. * * See [androidx.compose.ui.platform.LocalConfiguration] * [androidx.compose.ui.platform.LocalLifecycleOwner] [androidx.compose.ui.platform.LocalView] */ -val LocalActivity = - compositionLocalWithComputedDefaultOf { findOwner(LocalContext.currentValue) } +public val LocalActivity: ProvidableCompositionLocal = + compositionLocalWithComputedDefaultOf { + findOwner(LocalContext.currentValue) + } diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/ReportDrawn.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/ReportDrawn.kt index 3bc11f440b8a6..1e4f0b9ffdefe 100644 --- a/activity/activity-compose/src/main/java/androidx/activity/compose/ReportDrawn.kt +++ b/activity/activity-compose/src/main/java/androidx/activity/compose/ReportDrawn.kt @@ -82,7 +82,7 @@ private class ReportDrawnComposition( * Provides a [FullyDrawnReporterOwner] that can be used by Composables hosted in a * [androidx.activity.ComponentActivity]. */ -object LocalFullyDrawnReporterOwner { +public object LocalFullyDrawnReporterOwner { private val LocalFullyDrawnReporterOwner = compositionLocalOf { null } /** @@ -90,7 +90,7 @@ object LocalFullyDrawnReporterOwner { * one has not been set via [androidx.activity.setViewTreeFullyDrawnReporterOwner], nor is one * available by looking at the [LocalContext]. */ - val current: FullyDrawnReporterOwner? + public val current: FullyDrawnReporterOwner? @Composable get() = LocalFullyDrawnReporterOwner.current @@ -98,7 +98,7 @@ object LocalFullyDrawnReporterOwner { ?: findOwner(LocalContext.current) /** Associates a [LocalFullyDrawnReporterOwner] key to a value. */ - infix fun provides( + public infix fun provides( fullyDrawnReporterOwner: FullyDrawnReporterOwner ): ProvidedValue { return LocalFullyDrawnReporterOwner.provides(fullyDrawnReporterOwner) @@ -114,7 +114,7 @@ object LocalFullyDrawnReporterOwner { * @sample androidx.activity.compose.samples.ReportDrawnWhenSample */ @Composable -fun ReportDrawnWhen(predicate: () -> Boolean) { +public fun ReportDrawnWhen(predicate: () -> Boolean) { val fullyDrawnReporter = LocalFullyDrawnReporterOwner.current?.fullyDrawnReporter ?: return DisposableEffect(fullyDrawnReporter, predicate) { if (fullyDrawnReporter.isFullyDrawnReported) { @@ -133,7 +133,7 @@ fun ReportDrawnWhen(predicate: () -> Boolean) { * * @sample androidx.activity.compose.samples.ReportDrawnSample */ -@Composable fun ReportDrawn() = ReportDrawnWhen { true } +@Composable public fun ReportDrawn(): Unit = ReportDrawnWhen { true } /** * Adds [block] to the methods that must complete prior to [Activity.reportFullyDrawn] being called. @@ -146,7 +146,7 @@ fun ReportDrawnWhen(predicate: () -> Boolean) { * @sample androidx.activity.compose.samples.ReportDrawnAfterSample */ @Composable -fun ReportDrawnAfter(block: suspend () -> Unit) { +public fun ReportDrawnAfter(block: suspend () -> Unit) { val fullyDrawnReporter = LocalFullyDrawnReporterOwner.current?.fullyDrawnReporter ?: return LaunchedEffect(block, fullyDrawnReporter) { fullyDrawnReporter.reportWhenComplete(block) } } diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle index 3a8b5905070e8..49eaf04a67c42 100644 --- a/activity/activity/build.gradle +++ b/activity/activity/build.gradle @@ -57,7 +57,6 @@ androidx { type = SoftwareType.PUBLISHED_LIBRARY inceptionYear = "2018" description = "Provides the base Activity subclass and the relevant hooks to build a composable structure on top." - legacyDisableKotlinStrictApiMode = true } baselineProfile { diff --git a/activity/activity/src/main/java/androidx/activity/BackEventCompat.kt b/activity/activity/src/main/java/androidx/activity/BackEventCompat.kt index 15c7e3e7af6ad..d89c1fb8cf234 100644 --- a/activity/activity/src/main/java/androidx/activity/BackEventCompat.kt +++ b/activity/activity/src/main/java/androidx/activity/BackEventCompat.kt @@ -26,7 +26,7 @@ import androidx.annotation.VisibleForTesting import androidx.navigationevent.NavigationEvent /** Compat around the [BackEvent] class */ -class BackEventCompat +public class BackEventCompat @VisibleForTesting @JvmOverloads constructor( @@ -34,18 +34,18 @@ constructor( * Absolute X location of the touch point of this event in the coordinate space of the view that * * received this back event. */ - val touchX: Float, + public val touchX: Float, /** * Absolute Y location of the touch point of this event in the coordinate space of the view that * received this back event. */ - val touchY: Float, + public val touchY: Float, /** Value between 0 and 1 on how far along the back gesture is. */ - @FloatRange(from = 0.0, to = 1.0) val progress: Float, + @FloatRange(from = 0.0, to = 1.0) public val progress: Float, /** Indicates which edge the swipe starts from. */ - val swipeEdge: @SwipeEdge Int, + public val swipeEdge: @SwipeEdge Int, /** Frame time of the back event. */ - val frameTimeMillis: Long = 0, + public val frameTimeMillis: Long = 0, ) { /** @@ -57,7 +57,7 @@ constructor( * @param backEvent The [BackEvent] instance to convert. */ @RequiresApi(34) - constructor( + public constructor( backEvent: BackEvent ) : this( touchX = backEvent.touchX, @@ -75,7 +75,7 @@ constructor( * * @param navigationEvent The [NavigationEvent] instance to convert. */ - constructor( + public constructor( navigationEvent: NavigationEvent ) : this( touchX = navigationEvent.touchX, @@ -90,7 +90,7 @@ constructor( @RestrictTo(RestrictTo.Scope.LIBRARY) @Retention(AnnotationRetention.SOURCE) @IntDef(EDGE_LEFT, EDGE_RIGHT, EDGE_NONE) - annotation class SwipeEdge + public annotation class SwipeEdge /** * Convert this [BackEventCompat] object to a [BackEvent] object. @@ -99,7 +99,7 @@ constructor( * @throws UnsupportedOperationException if this API is called on an API prior to 34. */ @RequiresApi(34) - fun toBackEvent(): BackEvent { + public fun toBackEvent(): BackEvent { return if (Build.VERSION.SDK_INT >= 36) { BackEvent(touchX, touchY, progress, swipeEdge, frameTimeMillis) } else { @@ -112,7 +112,7 @@ constructor( * * @return A new [NavigationEvent] object populated with this [BackEventCompat] data. */ - fun toNavigationEvent(): NavigationEvent { + public fun toNavigationEvent(): NavigationEvent { return NavigationEvent( touchX = touchX, touchY = touchY, @@ -127,18 +127,18 @@ constructor( "swipeEdge=$swipeEdge, frameTimeMillis=$frameTimeMillis)" } - companion object { + public companion object { /** Indicates that the edge swipe starts from the left edge of the screen */ - const val EDGE_LEFT = 0 + public const val EDGE_LEFT: Int = 0 /** Indicates that the edge swipe starts from the right edge of the screen */ - const val EDGE_RIGHT = 1 + public const val EDGE_RIGHT: Int = 1 /** * Indicates that the back event was not triggered by an edge swipe back gesture. This * applies to cases like using the back button in 3-button navigation or pressing a hardware * back button. */ - const val EDGE_NONE = 2 + public const val EDGE_NONE: Int = 2 } } diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt index 85f84834a5d79..1291977b6d7c4 100644 --- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt +++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt @@ -111,7 +111,7 @@ import java.util.concurrent.atomic.AtomicInteger * level building blocks are included. Higher level components can then be used as needed without * enforcing a deep Activity class hierarchy or strong coupling between components. */ -open class ComponentActivity() : +public open class ComponentActivity() : androidx.core.app.ComponentActivity(), ContextAware, LifecycleOwner, @@ -143,7 +143,7 @@ open class ComponentActivity() : // Lazily recreated from NonConfigurationInstances by val viewModelStore private var _viewModelStore: ViewModelStore? = null private val reportFullyDrawnExecutor = createFullyDrawnExecutor() - override val fullyDrawnReporter by lazy { + override val fullyDrawnReporter: FullyDrawnReporter by lazy { FullyDrawnReporter(reportFullyDrawnExecutor) { reportFullyDrawn() } } @@ -325,7 +325,7 @@ open class ComponentActivity() : * required for API 27 and lower or when using the default [android.app.AppComponentFactory]. */ @ContentView - constructor(@LayoutRes contentLayoutId: Int) : this() { + public constructor(@LayoutRes contentLayoutId: Int) : this() { this.contentLayoutId = contentLayoutId } @@ -389,12 +389,12 @@ open class ComponentActivity() : * [lastCustomNonConfigurationInstance]. */ @Deprecated("Use a {@link androidx.lifecycle.ViewModel} to store non config state.") - open fun onRetainCustomNonConfigurationInstance(): Any? { + public open fun onRetainCustomNonConfigurationInstance(): Any? { return null } @get:Deprecated("Use a {@link androidx.lifecycle.ViewModel} to store non config state.") - open val lastCustomNonConfigurationInstance: Any? + public open val lastCustomNonConfigurationInstance: Any? /** Return the value previously returned from [onRetainCustomNonConfigurationInstance]. */ get() { val nc = lastNonConfigurationInstance as NonConfigurationInstances? @@ -430,7 +430,7 @@ open class ComponentActivity() : * attach listeners will see them already present. */ @CallSuper - open fun initializeViewTreeOwners() { + public open fun initializeViewTreeOwners() { window.decorView.setViewTreeLifecycleOwner(this) window.decorView.setViewTreeViewModelStoreOwner(this) window.decorView.setViewTreeSavedStateRegistryOwner(this) @@ -590,7 +590,7 @@ open class ComponentActivity() : /** * Called when the activity has detected the user's press of the back key. The * [onBackPressedDispatcher] will be given a chance to handle the back button before the default - * behavior of [android.app.Activity.onBackPressed] is invoked. + * behavior of [Activity.onBackPressed] is invoked. * * @see onBackPressedDispatcher */ diff --git a/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt b/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt index 20c1a230e19be..a5fa9d35a3664 100644 --- a/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt +++ b/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt @@ -38,7 +38,7 @@ import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner /** Base class for dialogs that enables composition of higher level components. */ -open class ComponentDialog +public open class ComponentDialog @JvmOverloads constructor(context: Context, @StyleRes themeResId: Int = 0) : Dialog(context, themeResId), @@ -153,7 +153,7 @@ constructor(context: Context, @StyleRes themeResId: Int = 0) : * attach listeners will see them already present. */ @CallSuper - open fun initializeViewTreeOwners() { + public open fun initializeViewTreeOwners() { window!!.decorView.setViewTreeLifecycleOwner(this) window!!.decorView.setViewTreeOnBackPressedDispatcherOwner(this) window!!.decorView.setViewTreeSavedStateRegistryOwner(this) diff --git a/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt b/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt index 9c21760bd5d85..2156d3de17944 100644 --- a/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt +++ b/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt @@ -76,7 +76,7 @@ private var Impl: EdgeToEdgeImpl? = null */ @JvmName("enable") @JvmOverloads -fun ComponentActivity.enableEdgeToEdge( +public fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim), ) { @@ -129,7 +129,7 @@ fun ComponentActivity.enableEdgeToEdge( } /** The style for the status bar or the navigation bar used in [enableEdgeToEdge]. */ -class SystemBarStyle +public class SystemBarStyle private constructor( private val lightScrim: Int, internal val darkScrim: Int, @@ -137,7 +137,7 @@ private constructor( internal val detectDarkMode: (Resources) -> Boolean, ) { - companion object { + public companion object { /** * Creates a new instance of [SystemBarStyle]. This style detects the dark mode @@ -161,7 +161,7 @@ private constructor( */ @JvmStatic @JvmOverloads - fun auto( + public fun auto( @ColorInt lightScrim: Int, @ColorInt darkScrim: Int, detectDarkMode: (Resources) -> Boolean = { resources -> @@ -185,7 +185,7 @@ private constructor( * the contrast against the light system icons. */ @JvmStatic - fun dark(@ColorInt scrim: Int): SystemBarStyle { + public fun dark(@ColorInt scrim: Int): SystemBarStyle { return SystemBarStyle( lightScrim = scrim, darkScrim = scrim, @@ -204,7 +204,7 @@ private constructor( * system icon color is always light. It is expected to be dark. */ @JvmStatic - fun light(@ColorInt scrim: Int, @ColorInt darkScrim: Int): SystemBarStyle { + public fun light(@ColorInt scrim: Int, @ColorInt darkScrim: Int): SystemBarStyle { return SystemBarStyle( lightScrim = scrim, darkScrim = darkScrim, diff --git a/activity/activity/src/main/java/androidx/activity/FullyDrawnReporter.kt b/activity/activity/src/main/java/androidx/activity/FullyDrawnReporter.kt index 44e32bfbe47a4..67ebc2f476b22 100644 --- a/activity/activity/src/main/java/androidx/activity/FullyDrawnReporter.kt +++ b/activity/activity/src/main/java/androidx/activity/FullyDrawnReporter.kt @@ -49,7 +49,10 @@ import java.util.concurrent.Executor * @param executor The [Executor] on which to call [reportFullyDrawn]. * @param reportFullyDrawn Will be called when all reporters have been removed. */ -class FullyDrawnReporter(private val executor: Executor, private val reportFullyDrawn: () -> Unit) { +public class FullyDrawnReporter( + private val executor: Executor, + private val reportFullyDrawn: () -> Unit, +) { private val lock = Any() @GuardedBy("lock") private var reporterCount = 0 @@ -62,7 +65,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * Returns `true` after [reportFullyDrawn] has been called or if backed by a [ComponentActivity] * and [ComponentActivity.reportFullyDrawn] has been called. */ - val isFullyDrawnReported: Boolean + public val isFullyDrawnReported: Boolean get() { return synchronized(lock) { reportedFullyDrawn } } @@ -80,7 +83,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully } /** Adds a lock to prevent calling [reportFullyDrawn]. */ - fun addReporter() { + public fun addReporter() { synchronized(lock) { if (!reportedFullyDrawn) { reporterCount++ @@ -92,7 +95,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * Removes a lock added in [addReporter]. When all locks have been removed, [reportFullyDrawn] * will be called on the next animation frame. */ - fun removeReporter() { + public fun removeReporter() { synchronized(lock) { if (!reportedFullyDrawn && reporterCount > 0) { reporterCount-- @@ -108,7 +111,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * Once [callback] has been called, it will be removed and [removeOnReportDrawnListener] does * not need to be called to remove it. */ - fun addOnReportDrawnListener(callback: () -> Unit) { + public fun addOnReportDrawnListener(callback: () -> Unit) { val callImmediately = synchronized(lock) { if (reportedFullyDrawn) { @@ -127,7 +130,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * Removes a previously registered [callback] so that it won't be called when [reportFullyDrawn] * is called by this class. */ - fun removeOnReportDrawnListener(callback: () -> Unit) { + public fun removeOnReportDrawnListener(callback: () -> Unit) { synchronized(lock) { onReportCallbacks -= callback } } @@ -137,7 +140,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * [Activity.reportFullyDrawn] has been called outside of this class. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - fun fullyDrawnReported() { + public fun fullyDrawnReported() { synchronized(lock) { reportedFullyDrawn = true onReportCallbacks.forEach { it() } @@ -162,7 +165,7 @@ class FullyDrawnReporter(private val executor: Executor, private val reportFully * Tells the [FullyDrawnReporter] to wait until [reporter] has completed before calling * [Activity.reportFullyDrawn]. */ -suspend inline fun FullyDrawnReporter.reportWhenComplete( +public suspend inline fun FullyDrawnReporter.reportWhenComplete( @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") reporter: suspend () -> Unit ) { addReporter() diff --git a/activity/activity/src/main/java/androidx/activity/FullyDrawnReporterOwner.kt b/activity/activity/src/main/java/androidx/activity/FullyDrawnReporterOwner.kt index 302f63de50061..2e0b4b47cbaa4 100644 --- a/activity/activity/src/main/java/androidx/activity/FullyDrawnReporterOwner.kt +++ b/activity/activity/src/main/java/androidx/activity/FullyDrawnReporterOwner.kt @@ -19,10 +19,10 @@ package androidx.activity * A class that has a [FullyDrawnReporter] that allows you to have separate parts of the UI * independently register when they have been fully loaded. */ -interface FullyDrawnReporterOwner { +public interface FullyDrawnReporterOwner { /** * Retrieve the [FullyDrawnReporter] that should handle the independent parts of the UI that * separately report that they are fully drawn. */ - val fullyDrawnReporter: FullyDrawnReporter + public val fullyDrawnReporter: FullyDrawnReporter } diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt index 21eb17e0b0b61..fb3f4cf80f2d4 100644 --- a/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt +++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt @@ -39,7 +39,7 @@ import java.util.concurrent.CopyOnWriteArrayList * @param enabled The default enabled state for this callback. * @see OnBackPressedDispatcher */ -abstract class OnBackPressedCallback(enabled: Boolean) { +public abstract class OnBackPressedCallback(enabled: Boolean) { /** * This [OnBackPressedCallback] class will delegate all interactions to [eventHandlers], which @@ -60,7 +60,7 @@ abstract class OnBackPressedCallback(enabled: Boolean) { */ @get:MainThread @set:MainThread - var isEnabled: Boolean = enabled + public var isEnabled: Boolean = enabled set(value) { field = value for (callback in eventHandlers) { @@ -74,7 +74,7 @@ abstract class OnBackPressedCallback(enabled: Boolean) { /** Removes this callback from any [OnBackPressedDispatcher] it is currently added to. */ @MainThread - fun remove() { + public fun remove() { for (closeable in closeables) { closeable.close() } @@ -93,7 +93,7 @@ abstract class OnBackPressedCallback(enabled: Boolean) { */ @Suppress("CallbackMethodName") /* mirror handleOnBackPressed local style */ @MainThread - open fun handleOnBackStarted(backEvent: BackEventCompat) {} + public open fun handleOnBackStarted(backEvent: BackEventCompat) {} /** * Callback for handling the system UI generated equivalent to @@ -103,10 +103,10 @@ abstract class OnBackPressedCallback(enabled: Boolean) { */ @Suppress("CallbackMethodName") /* mirror handleOnBackPressed local style */ @MainThread - open fun handleOnBackProgressed(backEvent: BackEventCompat) {} + public open fun handleOnBackProgressed(backEvent: BackEventCompat) {} /** Callback for handling the [OnBackPressedDispatcher.onBackPressed] event. */ - @MainThread abstract fun handleOnBackPressed() + @MainThread public abstract fun handleOnBackPressed() /** * Callback for handling the system UI generated equivalent to @@ -116,7 +116,7 @@ abstract class OnBackPressedCallback(enabled: Boolean) { */ @Suppress("CallbackMethodName") /* mirror handleOnBackPressed local style */ @MainThread - open fun handleOnBackCancelled() {} + public open fun handleOnBackCancelled() {} internal fun addCloseable(closeable: AutoCloseable) { closeables += closeable diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt index c2bf76bc9c9eb..17862b63f2948 100644 --- a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt +++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt @@ -67,7 +67,7 @@ import androidx.navigationevent.OnBackInvokedOverlayInput // fallbackOnBackPressed. To avoid silently breaking source compatibility the new // primary constructor has no optional parameters to avoid ambiguity/wrong overload resolution // when a single parameter is provided as a trailing lambda. -class OnBackPressedDispatcher( +public class OnBackPressedDispatcher( @Suppress("unused") private val fallbackOnBackPressed: Runnable?, @Suppress("unused") private val onHasEnabledCallbacksChanged: Consumer?, ) { @@ -75,18 +75,10 @@ class OnBackPressedDispatcher( private var hasEnabledCallbacks = false /** - * Bridges this dispatcher to the underlying [NavigationEventDispatcher] lazily. - * - * Initialization of the [NavigationEventDispatcher] involves loading the `navigation-event` - * library classes. We use an anonymous object to hold the dispatcher and input together, - * ensuring they are initialized and linked atomically only when first accessed. + * Input source representing back events initiated by this dispatcher (for example, via a direct + * call to [onBackPressed]). */ - private val impl by lazy { - object { - val dispatcher = NavigationEventDispatcher { fallbackOnBackPressed?.run() } - val input = OnBackPressedEventInput().also { dispatcher.addInput(it) } - } - } + private val eventInput by lazy { OnBackPressedEventInput() } /** * This [OnBackPressedDispatcher] class will delegate all interactions to [eventDispatcher], @@ -96,17 +88,10 @@ class OnBackPressedDispatcher( * @see [OnBackPressedCallback.eventHandlers] */ internal val eventDispatcher - get() = impl.dispatcher - - /** - * Input source representing back events initiated by this dispatcher (for example, via a direct - * call to [onBackPressed]). - */ - private val eventInput - get() = impl.input + get() = eventInput.dispatcher @JvmOverloads - constructor(fallbackOnBackPressed: Runnable? = null) : this(fallbackOnBackPressed, null) + public constructor(fallbackOnBackPressed: Runnable? = null) : this(fallbackOnBackPressed, null) /** * Sets the [OnBackInvokedDispatcher] for handling system back for Android SDK T+. @@ -114,7 +99,7 @@ class OnBackPressedDispatcher( * @param invoker the OnBackInvokedDispatcher to be set on this dispatcher */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) - fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { + public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { eventDispatcher.addInput( OnBackInvokedDefaultInput(invoker), NavigationEventDispatcher.PRIORITY_DEFAULT, @@ -138,7 +123,7 @@ class OnBackPressedDispatcher( * @see onBackPressed */ @MainThread - fun addCallback(onBackPressedCallback: OnBackPressedCallback) { + public fun addCallback(onBackPressedCallback: OnBackPressedCallback) { val info = OnBackPressedCallbackInfo(onBackPressedCallback) val handler = onBackPressedCallback.createNavigationEventHandler(info) eventDispatcher.addHandler(handler) @@ -168,7 +153,7 @@ class OnBackPressedDispatcher( */ @MainThread @OptIn(ExperimentalActivityApi::class) - fun addCallback(owner: LifecycleOwner, onBackPressedCallback: OnBackPressedCallback) { + public fun addCallback(owner: LifecycleOwner, onBackPressedCallback: OnBackPressedCallback) { val lifecycle = owner.lifecycle if (lifecycle.currentState === State.DESTROYED) { @@ -236,17 +221,17 @@ class OnBackPressedDispatcher( * * @return True if there is at least one enabled callback. */ - @MainThread fun hasEnabledCallbacks(): Boolean = hasEnabledCallbacks + @MainThread public fun hasEnabledCallbacks(): Boolean = hasEnabledCallbacks @VisibleForTesting @MainThread - fun dispatchOnBackStarted(backEvent: BackEventCompat) { + public fun dispatchOnBackStarted(backEvent: BackEventCompat) { eventInput.backStarted(backEvent.toNavigationEvent()) } @VisibleForTesting @MainThread - fun dispatchOnBackProgressed(backEvent: BackEventCompat) { + public fun dispatchOnBackProgressed(backEvent: BackEventCompat) { eventInput.backProgressed(backEvent.toNavigationEvent()) } @@ -259,13 +244,13 @@ class OnBackPressedDispatcher( * set by the constructor will be triggered. */ @MainThread - fun onBackPressed() { + public fun onBackPressed() { eventInput.backCompleted() } @VisibleForTesting @MainThread - fun dispatchOnBackCancelled() { + public fun dispatchOnBackCancelled() { eventInput.backCancelled() } @@ -278,6 +263,15 @@ class OnBackPressedDispatcher( */ private inner class OnBackPressedEventInput : NavigationEventInput() { + /** + * The underlying dispatcher that coordinates back events. + * + * This is hosted here and initialized lazily alongside the input to ensure they are linked + * atomically. + */ + val dispatcher = + NavigationEventDispatcher { fallbackOnBackPressed?.run() }.also { it.addInput(this) } + /** * Syncs the enabled-handler count back to [OnBackPressedDispatcher]. * @@ -327,7 +321,7 @@ class OnBackPressedDispatcher( * dispatch ordering across lifecycle transitions. */ @Suppress("RegistrationName") -fun OnBackPressedDispatcher.addCallback( +public fun OnBackPressedDispatcher.addCallback( owner: LifecycleOwner? = null, enabled: Boolean = true, onBackPressed: OnBackPressedCallback.() -> Unit, diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt index 6e9bae8387193..e7e206ad84a02 100644 --- a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt +++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.LifecycleOwner * * @see OnBackPressedDispatcher */ -interface OnBackPressedDispatcherOwner : LifecycleOwner { +public interface OnBackPressedDispatcherOwner : LifecycleOwner { /** The [OnBackPressedDispatcher] that should handle the system back button. */ - val onBackPressedDispatcher: OnBackPressedDispatcher + public val onBackPressedDispatcher: OnBackPressedDispatcher } diff --git a/activity/activity/src/main/java/androidx/activity/ViewTreeFullyLoadedReporterOwner.kt b/activity/activity/src/main/java/androidx/activity/ViewTreeFullyLoadedReporterOwner.kt index 229426beed1ac..992023257a2b9 100644 --- a/activity/activity/src/main/java/androidx/activity/ViewTreeFullyLoadedReporterOwner.kt +++ b/activity/activity/src/main/java/androidx/activity/ViewTreeFullyLoadedReporterOwner.kt @@ -31,7 +31,9 @@ import androidx.core.viewtree.getParentOrViewTreeDisjointParent * @param fullyDrawnReporterOwner [FullyDrawnReporterOwner] associated with the [View] */ @JvmName("set") -fun View.setViewTreeFullyDrawnReporterOwner(fullyDrawnReporterOwner: FullyDrawnReporterOwner) { +public fun View.setViewTreeFullyDrawnReporterOwner( + fullyDrawnReporterOwner: FullyDrawnReporterOwner +) { setTag(R.id.report_drawn, fullyDrawnReporterOwner) } @@ -43,7 +45,7 @@ fun View.setViewTreeFullyDrawnReporterOwner(fullyDrawnReporterOwner: FullyDrawnR * ancestors */ @JvmName("get") -fun View.findViewTreeFullyDrawnReporterOwner(): FullyDrawnReporterOwner? { +public fun View.findViewTreeFullyDrawnReporterOwner(): FullyDrawnReporterOwner? { var currentView: View? = this while (currentView != null) { val reporterOwner = currentView.getTag(R.id.report_drawn) as? FullyDrawnReporterOwner diff --git a/activity/activity/src/main/java/androidx/activity/ViewTreeOnBackPressedDispatcherOwner.kt b/activity/activity/src/main/java/androidx/activity/ViewTreeOnBackPressedDispatcherOwner.kt index e41adc585c2cd..eafa800cbcf99 100644 --- a/activity/activity/src/main/java/androidx/activity/ViewTreeOnBackPressedDispatcherOwner.kt +++ b/activity/activity/src/main/java/androidx/activity/ViewTreeOnBackPressedDispatcherOwner.kt @@ -33,7 +33,7 @@ import androidx.core.viewtree.getParentOrViewTreeDisjointParent * @param onBackPressedDispatcherOwner [OnBackPressedDispatcherOwner] associated with the [View] */ @JvmName("set") -fun View.setViewTreeOnBackPressedDispatcherOwner( +public fun View.setViewTreeOnBackPressedDispatcherOwner( onBackPressedDispatcherOwner: OnBackPressedDispatcherOwner ) { setTag(R.id.view_tree_on_back_pressed_dispatcher_owner, onBackPressedDispatcherOwner) @@ -47,7 +47,7 @@ fun View.setViewTreeOnBackPressedDispatcherOwner( * ancestors */ @JvmName("get") -fun View.findViewTreeOnBackPressedDispatcherOwner(): OnBackPressedDispatcherOwner? { +public fun View.findViewTreeOnBackPressedDispatcherOwner(): OnBackPressedDispatcherOwner? { var currentView: View? = this while (currentView != null) { val dispatchOwner = diff --git a/activity/activity/src/main/java/androidx/activity/contextaware/ContextAware.kt b/activity/activity/src/main/java/androidx/activity/contextaware/ContextAware.kt index c74cde3cc358f..f295c034f4741 100644 --- a/activity/activity/src/main/java/androidx/activity/contextaware/ContextAware.kt +++ b/activity/activity/src/main/java/androidx/activity/contextaware/ContextAware.kt @@ -28,14 +28,14 @@ import kotlinx.coroutines.suspendCancellableCoroutine * * @see ContextAwareHelper */ -interface ContextAware { +public interface ContextAware { /** * Get the [Context] if it is currently available. If this returns `null`, you can use * [addOnContextAvailableListener] to receive a callback for when it available. * * @return the Context if it is currently available. */ - fun peekAvailableContext(): Context? + public fun peekAvailableContext(): Context? /** * Add a new [OnContextAvailableListener] for receiving a callback for when this class is @@ -48,7 +48,7 @@ interface ContextAware { * @param listener The listener that should be added. * @see removeOnContextAvailableListener */ - fun addOnContextAvailableListener(listener: OnContextAvailableListener) + public fun addOnContextAvailableListener(listener: OnContextAvailableListener) /** * Remove a [OnContextAvailableListener] previously added via [addOnContextAvailableListener]. @@ -56,7 +56,7 @@ interface ContextAware { * @param listener The listener that should be removed. * @see addOnContextAvailableListener */ - fun removeOnContextAvailableListener(listener: OnContextAvailableListener) + public fun removeOnContextAvailableListener(listener: OnContextAvailableListener) } /** @@ -66,7 +66,7 @@ interface ContextAware { * current coroutine context. Otherwise, [onContextAvailable] will be called on the UI thread * immediately when the Context becomes available. */ -suspend inline fun ContextAware.withContextAvailable( +public suspend inline fun ContextAware.withContextAvailable( crossinline onContextAvailable: (@JvmSuppressWildcards Context) -> @JvmSuppressWildcards R ): @JvmSuppressWildcards R { val availableContext = peekAvailableContext() diff --git a/activity/activity/src/main/java/androidx/activity/contextaware/ContextAwareHelper.kt b/activity/activity/src/main/java/androidx/activity/contextaware/ContextAwareHelper.kt index 4bef4aa733833..68444233384c8 100644 --- a/activity/activity/src/main/java/androidx/activity/contextaware/ContextAwareHelper.kt +++ b/activity/activity/src/main/java/androidx/activity/contextaware/ContextAwareHelper.kt @@ -29,7 +29,7 @@ import java.util.concurrent.CopyOnWriteArraySet * Listeners added after the context has been made available via [dispatchOnContextAvailable] will * have the Context synchronously delivered to them up until [clearAvailableContext] is called. */ -class ContextAwareHelper { +public class ContextAwareHelper { private val listeners: MutableSet = CopyOnWriteArraySet() @Volatile private var context: Context? = null @@ -40,7 +40,7 @@ class ContextAwareHelper { * * @return the Context if it is currently available. */ - fun peekAvailableContext(): Context? { + public fun peekAvailableContext(): Context? { return context } @@ -51,7 +51,7 @@ class ContextAwareHelper { * @param listener The listener that should be added. * @see removeOnContextAvailableListener */ - fun addOnContextAvailableListener(listener: OnContextAvailableListener) { + public fun addOnContextAvailableListener(listener: OnContextAvailableListener) { context?.let { listener.onContextAvailable(it) } listeners.add(listener) } @@ -62,7 +62,7 @@ class ContextAwareHelper { * @param listener The listener that should be removed. * @see addOnContextAvailableListener */ - fun removeOnContextAvailableListener(listener: OnContextAvailableListener) { + public fun removeOnContextAvailableListener(listener: OnContextAvailableListener) { listeners.remove(listener) } @@ -72,7 +72,7 @@ class ContextAwareHelper { * * @param context The [Context] the [ContextAware] object is now associated with. */ - fun dispatchOnContextAvailable(context: Context) { + public fun dispatchOnContextAvailable(context: Context) { this.context = context for (listener in listeners) { listener.onContextAvailable(context) @@ -80,7 +80,7 @@ class ContextAwareHelper { } /** Clear any [Context] previously made available via [dispatchOnContextAvailable]. */ - fun clearAvailableContext() { + public fun clearAvailableContext() { context = null } } diff --git a/activity/activity/src/main/java/androidx/activity/contextaware/OnContextAvailableListener.kt b/activity/activity/src/main/java/androidx/activity/contextaware/OnContextAvailableListener.kt index e8124766b2455..6fa1a3470ac62 100644 --- a/activity/activity/src/main/java/androidx/activity/contextaware/OnContextAvailableListener.kt +++ b/activity/activity/src/main/java/androidx/activity/contextaware/OnContextAvailableListener.kt @@ -23,12 +23,12 @@ import android.content.Context * * @see ContextAware.addOnContextAvailableListener */ -fun interface OnContextAvailableListener { +public fun interface OnContextAvailableListener { /** * Called when the [ContextAware] object this listener was added to is associated to a * [Context]. * * @param context The [Context] the [ContextAware] object is now associated with. */ - fun onContextAvailable(context: Context) + public fun onContextAvailable(context: Context) } diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResult.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResult.kt index 27cec58790199..a705130f5c39b 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResult.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResult.kt @@ -30,12 +30,12 @@ import android.os.Parcelable * @see Activity.onActivityResult */ @SuppressLint("BanParcelableUsage") -class ActivityResult( +public class ActivityResult( /** Status to indicate the success of the operation */ - val resultCode: Int, + public val resultCode: Int, /** The intent that carries the result data */ - val data: Intent?, + public val data: Intent?, ) : Parcelable { internal constructor( @@ -55,16 +55,16 @@ class ActivityResult( data?.writeToParcel(dest, flags) } - override fun describeContents() = 0 + override fun describeContents(): Int = 0 - companion object { + public companion object { /** * A readable representation of standard activity result codes for the given [resultCode] * * @return RESULT_OK, RESULT_CANCELED, or the number otherwise */ @JvmStatic - fun resultCodeToString(resultCode: Int): String { + public fun resultCodeToString(resultCode: Int): String { return when (resultCode) { Activity.RESULT_OK -> "RESULT_OK" Activity.RESULT_CANCELED -> "RESULT_CANCELED" @@ -74,7 +74,7 @@ class ActivityResult( @Suppress("unused") @JvmField - val CREATOR = + public val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(parcel: Parcel) = ActivityResult(parcel) @@ -88,11 +88,11 @@ class ActivityResult( * * @return the resultCode of the [ActivityResult] */ -operator fun ActivityResult.component1(): Int = resultCode +public operator fun ActivityResult.component1(): Int = resultCode /** * Destructuring declaration for [ActivityResult] to provide the intent * * @return the intent of the [ActivityResult] */ -operator fun ActivityResult.component2(): Intent? = data +public operator fun ActivityResult.component2(): Intent? = data diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCallback.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCallback.kt index 21170ec2c0e82..b2479089edb29 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCallback.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCallback.kt @@ -21,7 +21,7 @@ import android.app.Activity * A type-safe callback to be called when an [activity result][Activity.onActivityResult] is * available. */ -fun interface ActivityResultCallback { +public fun interface ActivityResultCallback { /** Called when result is available */ - fun onActivityResult(result: O) + public fun onActivityResult(result: O) } diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.kt index af63798e8e33e..382d5599f62a5 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.kt @@ -28,7 +28,7 @@ import androidx.core.app.ActivityOptionsCompat * A class that can call [Activity.startActivityForResult]-style APIs without having to manage * request codes, and converting request/response to an [Intent] */ -interface ActivityResultCaller { +public interface ActivityResultCaller { /** * Register a request to [start an activity for result][Activity.startActivityForResult], * designated by the given [contract][ActivityResultContract]. @@ -46,7 +46,7 @@ interface ActivityResultCaller { * available * @return the launcher that can be used to start the activity or dispose of the prepared call. */ - fun registerForActivityResult( + public fun registerForActivityResult( contract: ActivityResultContract, callback: ActivityResultCallback, ): ActivityResultLauncher @@ -69,7 +69,7 @@ interface ActivityResultCaller { * available * @return the launcher that can be used to start the activity or dispose of the prepared call. */ - fun registerForActivityResult( + public fun registerForActivityResult( contract: ActivityResultContract, registry: ActivityResultRegistry, callback: ActivityResultCallback, @@ -82,7 +82,7 @@ interface ActivityResultCaller { * * @see ActivityResultCaller.registerForActivityResult */ -fun ActivityResultCaller.registerForActivityResult( +public fun ActivityResultCaller.registerForActivityResult( contract: ActivityResultContract, input: I, registry: ActivityResultRegistry, @@ -98,7 +98,7 @@ fun ActivityResultCaller.registerForActivityResult( * * @see ActivityResultCaller.registerForActivityResult */ -fun ActivityResultCaller.registerForActivityResult( +public fun ActivityResultCaller.registerForActivityResult( contract: ActivityResultContract, input: I, callback: (@JvmSuppressWildcards O) -> Unit, diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.kt index 1183f8e38a9fc..f743cbad099f9 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.kt @@ -27,7 +27,7 @@ import androidx.core.app.ActivityOptionsCompat * start the process of executing an [ActivityResultContract] that takes an [I] as its required * input. */ -abstract class ActivityResultLauncher { +public abstract class ActivityResultLauncher { /** * Executes an [ActivityResultContract] given the required [input]. * @@ -36,7 +36,7 @@ abstract class ActivityResultLauncher { * * @throws android.content.ActivityNotFoundException */ - open fun launch(input: I) { + public open fun launch(input: I) { launch(input, null) } @@ -49,7 +49,7 @@ abstract class ActivityResultLauncher { * * @throws android.content.ActivityNotFoundException */ - abstract fun launch(input: I, options: ActivityOptionsCompat?) + public abstract fun launch(input: I, options: ActivityOptionsCompat?) /** * Unregisters this launcher, releasing the underlying result callback, and any references @@ -58,19 +58,19 @@ abstract class ActivityResultLauncher { * You should call this if the registry may live longer than the callback registered for this * launcher. */ - @MainThread abstract fun unregister() + @MainThread public abstract fun unregister() /** Returns the [ActivityResultContract] that was used to create this launcher. */ - abstract val contract: ActivityResultContract + public abstract val contract: ActivityResultContract } /** Convenience method to launch a no-argument registered call without needing to pass in `null`. */ -fun ActivityResultLauncher.launch(options: ActivityOptionsCompat? = null) { +public fun ActivityResultLauncher.launch(options: ActivityOptionsCompat? = null) { launch(null, options) } /** Convenience method to launch a no-argument registered call without needing to pass in `Unit`. */ @JvmName("launchUnit") -fun ActivityResultLauncher.launch(options: ActivityOptionsCompat? = null) { +public fun ActivityResultLauncher.launch(options: ActivityOptionsCompat? = null) { launch(Unit, options) } diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.kt index 7dbabd09e1972..9e4639067c804 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.kt @@ -39,7 +39,7 @@ import kotlin.random.Random.Default.nextInt * When testing, make sure to explicitly provide a registry instance whenever calling * [ActivityResultCaller.registerForActivityResult], to be able to inject a test instance. */ -abstract class ActivityResultRegistry { +public abstract class ActivityResultRegistry { private val rcToKey = mutableMapOf() private val keyToRc = mutableMapOf() private val keyToLifecycleContainers = mutableMapOf() @@ -59,7 +59,7 @@ abstract class ActivityResultRegistry { * @param options Additional options for how the Activity should be started. */ @MainThread - abstract fun onLaunch( + public abstract fun onLaunch( requestCode: Int, contract: ActivityResultContract, input: I, @@ -78,7 +78,7 @@ abstract class ActivityResultRegistry { * @param callback the activity result callback * @return a launcher that can be used to execute an ActivityResultContract. */ - fun register( + public fun register( key: String, lifecycleOwner: LifecycleOwner, contract: ActivityResultContract, @@ -158,7 +158,7 @@ abstract class ActivityResultRegistry { * @param callback the activity result callback * @return a launcher that can be used to execute an ActivityResultContract. */ - fun register( + public fun register( key: String, contract: ActivityResultContract, callback: ActivityResultCallback, @@ -242,7 +242,7 @@ abstract class ActivityResultRegistry { * * @param outState the place to put state into */ - fun onSaveInstanceState(outState: Bundle) { + public fun onSaveInstanceState(outState: Bundle) { outState.putIntegerArrayList( KEY_COMPONENT_ACTIVITY_REGISTERED_RCS, ArrayList(keyToRc.values), @@ -257,7 +257,7 @@ abstract class ActivityResultRegistry { * * @param savedInstanceState the place to restore from */ - fun onRestoreInstanceState(savedInstanceState: Bundle?) { + public fun onRestoreInstanceState(savedInstanceState: Bundle?) { if (savedInstanceState == null) { return } @@ -306,7 +306,7 @@ abstract class ActivityResultRegistry { * will be called. */ @MainThread - fun dispatchResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + public fun dispatchResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { val key = rcToKey[requestCode] ?: return false doDispatch(key, resultCode, data, keyToCallback[key]) return true @@ -320,7 +320,7 @@ abstract class ActivityResultRegistry { * @return true if there is a callback registered for the given request code, false otherwise. */ @MainThread - fun dispatchResult(requestCode: Int, result: O): Boolean { + public fun dispatchResult(requestCode: Int, result: O): Boolean { val key = rcToKey[requestCode] ?: return false val callbackAndContract = keyToCallback[key] if (callbackAndContract?.callback == null) { diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt index eccf4a0f44b03..6badb2335811e 100644 --- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt +++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt @@ -25,11 +25,11 @@ package androidx.activity.result * * @see ActivityResultRegistry */ -interface ActivityResultRegistryOwner { +public interface ActivityResultRegistryOwner { /** * Returns the ActivityResultRegistry of the provider. * * @return The activity result registry of the provider. */ - val activityResultRegistry: ActivityResultRegistry + public val activityResultRegistry: ActivityResultRegistry } diff --git a/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt b/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt index 9cdafff2b955a..2b625c6182a40 100644 --- a/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt +++ b/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt @@ -29,19 +29,19 @@ import androidx.annotation.IntDef * Contract. */ @SuppressLint("BanParcelableUsage") -class IntentSenderRequest +public class IntentSenderRequest internal constructor( /** The intentSender from this IntentSenderRequest. */ - val intentSender: IntentSender, + public val intentSender: IntentSender, /** * The intent from this IntentSender request. If non-null, this will be provided as the intent * parameter to IntentSender#sendIntent. */ - val fillInIntent: Intent? = null, + public val fillInIntent: Intent? = null, /** The flag mask from this IntentSender request. */ - val flagsMask: Int = 0, + public val flagsMask: Int = 0, /** The flag values from this IntentSender request. */ - val flagsValues: Int = 0, + public val flagsValues: Int = 0, ) : Parcelable { @Suppress("DEPRECATION") @@ -66,7 +66,7 @@ internal constructor( } /** A builder for constructing [IntentSenderRequest] instances. */ - class Builder(private val intentSender: IntentSender) { + public class Builder(private val intentSender: IntentSender) { private var fillInIntent: Intent? = null private var flagsMask = 0 private var flagsValues = 0 @@ -77,7 +77,7 @@ internal constructor( * @param pendingIntent the pendingIntent containing with the intentSender to go in the * IntentSenderRequest. */ - constructor(pendingIntent: PendingIntent) : this(pendingIntent.intentSender) + public constructor(pendingIntent: PendingIntent) : this(pendingIntent.intentSender) @IntDef( flag = true, @@ -123,7 +123,7 @@ internal constructor( * provided as the intent parameter to IntentSender#sendIntent. * @return This builder. */ - fun setFillInIntent(fillInIntent: Intent?): Builder { + public fun setFillInIntent(fillInIntent: Intent?): Builder { this.fillInIntent = fillInIntent return this } @@ -137,7 +137,7 @@ internal constructor( * IntentSender that you would like to change. * @return This builder. */ - fun setFlags(@Flag values: Int, mask: Int): Builder { + public fun setFlags(@Flag values: Int, mask: Int): Builder { flagsValues = values flagsMask = mask return this @@ -148,15 +148,15 @@ internal constructor( * * @return the newly constructed IntentSenderRequest. */ - fun build(): IntentSenderRequest { + public fun build(): IntentSenderRequest { return IntentSenderRequest(intentSender, fillInIntent, flagsMask, flagsValues) } } - companion object { + public companion object { @Suppress("unused") @JvmField - val CREATOR: Parcelable.Creator = + public val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(inParcel: Parcel): IntentSenderRequest { return IntentSenderRequest(inParcel) diff --git a/activity/activity/src/main/java/androidx/activity/result/PickVisualMediaRequest.kt b/activity/activity/src/main/java/androidx/activity/result/PickVisualMediaRequest.kt index 7969c0d41cc21..77767a37244da 100644 --- a/activity/activity/src/main/java/androidx/activity/result/PickVisualMediaRequest.kt +++ b/activity/activity/src/main/java/androidx/activity/result/PickVisualMediaRequest.kt @@ -28,8 +28,7 @@ import androidx.annotation.IntRange import androidx.annotation.RequiresApi /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param mediaType type to go into the PickVisualMediaRequest @@ -39,12 +38,12 @@ import androidx.annotation.RequiresApi "Superseded by PickVisualMediaRequest that takes an optional maxItems", level = DeprecationLevel.HIDDEN, ) // Binary API compatibility. -fun PickVisualMediaRequest(mediaType: VisualMediaType = ImageAndVideo) = - PickVisualMediaRequest.Builder().setMediaType(mediaType).build() +public fun PickVisualMediaRequest( + mediaType: VisualMediaType = ImageAndVideo +): PickVisualMediaRequest = PickVisualMediaRequest.Builder().setMediaType(mediaType).build() /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param mediaType type to go into the PickVisualMediaRequest @@ -56,14 +55,14 @@ fun PickVisualMediaRequest(mediaType: VisualMediaType = ImageAndVideo) = level = DeprecationLevel.HIDDEN, ) // Binary API compatibility. @Suppress("MissingJvmstatic") -fun PickVisualMediaRequest( +public fun PickVisualMediaRequest( mediaType: VisualMediaType = ImageAndVideo, @IntRange(from = 2) maxItems: Int = PickMultipleVisualMedia.getMaxItems(), -) = PickVisualMediaRequest.Builder().setMediaType(mediaType).setMaxItems(maxItems).build() +): PickVisualMediaRequest = + PickVisualMediaRequest.Builder().setMediaType(mediaType).setMaxItems(maxItems).build() /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param mediaType type to go into the PickVisualMediaRequest @@ -77,12 +76,12 @@ fun PickVisualMediaRequest( * @return a PickVisualMediaRequest that contains the given input */ @Suppress("MissingJvmstatic") -fun PickVisualMediaRequest( +public fun PickVisualMediaRequest( mediaType: VisualMediaType = ImageAndVideo, @IntRange(from = 2) maxItems: Int = PickMultipleVisualMedia.getMaxItems(), isOrderedSelection: Boolean = false, defaultTab: DefaultTab = DefaultTab.PhotosTab, -) = +): PickVisualMediaRequest = PickVisualMediaRequest.Builder() .setMediaType(mediaType) .setMaxItems(maxItems) @@ -91,8 +90,7 @@ fun PickVisualMediaRequest( .build() /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param accentColor color long to customize picker accent color. Note that the support for this @@ -109,13 +107,13 @@ fun PickVisualMediaRequest( * @return a PickVisualMediaRequest that contains the given input */ @Suppress("MissingJvmstatic") -fun PickVisualMediaRequest( +public fun PickVisualMediaRequest( accentColor: Long, mediaType: VisualMediaType = ImageAndVideo, @IntRange(from = 2) maxItems: Int = PickMultipleVisualMedia.getMaxItems(), isOrderedSelection: Boolean = false, defaultTab: DefaultTab = DefaultTab.PhotosTab, -) = +): PickVisualMediaRequest = PickVisualMediaRequest.Builder() .setMediaType(mediaType) .setMaxItems(maxItems) @@ -125,8 +123,7 @@ fun PickVisualMediaRequest( .build() /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param mediaCapabilitiesForTranscoding the [MediaCapabilities] that the application can handle. @@ -139,13 +136,13 @@ fun PickVisualMediaRequest( */ @Suppress("MissingJvmstatic") @RequiresApi(Build.VERSION_CODES.TIRAMISU) -fun PickVisualMediaRequest( +public fun PickVisualMediaRequest( mediaCapabilitiesForTranscoding: MediaCapabilities?, mediaType: VisualMediaType = ImageAndVideo, @IntRange(from = 2) maxItems: Int = PickMultipleVisualMedia.getMaxItems(), isOrderedSelection: Boolean = false, defaultTab: DefaultTab = DefaultTab.PhotosTab, -) = +): PickVisualMediaRequest = PickVisualMediaRequest.Builder() .setMediaType(mediaType) .setMaxItems(maxItems) @@ -155,8 +152,7 @@ fun PickVisualMediaRequest( .build() /** - * Creates a request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * Creates a request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. * * @param mediaCapabilitiesForTranscoding the [MediaCapabilities] that the application can handle. @@ -170,14 +166,14 @@ fun PickVisualMediaRequest( */ @Suppress("MissingJvmstatic") @RequiresApi(Build.VERSION_CODES.TIRAMISU) -fun PickVisualMediaRequest( +public fun PickVisualMediaRequest( mediaCapabilitiesForTranscoding: MediaCapabilities?, accentColor: Long, mediaType: VisualMediaType = ImageAndVideo, @IntRange(from = 2) maxItems: Int = PickMultipleVisualMedia.getMaxItems(), isOrderedSelection: Boolean = false, defaultTab: DefaultTab = DefaultTab.PhotosTab, -) = +): PickVisualMediaRequest = PickVisualMediaRequest.Builder() .setMediaType(mediaType) .setMaxItems(maxItems) @@ -188,35 +184,34 @@ fun PickVisualMediaRequest( .build() /** - * A request for a - * [androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia] or + * A request for a [PickMultipleVisualMedia] or * [androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia] Activity Contract. */ -class PickVisualMediaRequest internal constructor() { +public class PickVisualMediaRequest internal constructor() { - var mediaType: VisualMediaType = ImageAndVideo + public var mediaType: VisualMediaType = ImageAndVideo internal set - var maxItems: Int = PickMultipleVisualMedia.getMaxItems() + public var maxItems: Int = PickMultipleVisualMedia.getMaxItems() internal set - var isOrderedSelection: Boolean = false + public var isOrderedSelection: Boolean = false internal set - var defaultTab: DefaultTab = DefaultTab.PhotosTab + public var defaultTab: DefaultTab = DefaultTab.PhotosTab internal set - var isCustomAccentColorApplied: Boolean = false + public var isCustomAccentColorApplied: Boolean = false internal set - var accentColor: Long = 0 + public var accentColor: Long = 0 internal set - var mediaCapabilitiesForTranscoding: MediaCapabilities? = null + public var mediaCapabilitiesForTranscoding: MediaCapabilities? = null internal set /** A builder for constructing [PickVisualMediaRequest] instances. */ - class Builder { + public class Builder { private var mediaType: VisualMediaType = ImageAndVideo private var maxItems: Int = PickMultipleVisualMedia.getMaxItems() @@ -235,7 +230,7 @@ class PickVisualMediaRequest internal constructor() { * @param mediaType type to go into the PickVisualMediaRequest * @return This builder. */ - fun setMediaType(mediaType: VisualMediaType): Builder { + public fun setMediaType(mediaType: VisualMediaType): Builder { this.mediaType = mediaType return this } @@ -247,7 +242,7 @@ class PickVisualMediaRequest internal constructor() { * @param maxItems int type limiting the number of selectable items * @return This builder. */ - fun setMaxItems(@IntRange(from = 2) maxItems: Int): Builder { + public fun setMaxItems(@IntRange(from = 2) maxItems: Int): Builder { this.maxItems = maxItems return this } @@ -261,7 +256,7 @@ class PickVisualMediaRequest internal constructor() { * @param isOrderedSelection boolean to enable customisable selection order in the picker * @return This builder. */ - fun setOrderedSelection(isOrderedSelection: Boolean): Builder { + public fun setOrderedSelection(isOrderedSelection: Boolean): Builder { this.isOrderedSelection = isOrderedSelection return this } @@ -277,7 +272,7 @@ class PickVisualMediaRequest internal constructor() { * @return This builder. * @see android.provider.MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB */ - fun setDefaultTab(defaultTab: DefaultTab): Builder { + public fun setDefaultTab(defaultTab: DefaultTab): Builder { this.defaultTab = defaultTab return this } @@ -292,7 +287,7 @@ class PickVisualMediaRequest internal constructor() { * @return This builder. * @see android.provider.MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR */ - fun setAccentColor(accentColor: Long): Builder { + public fun setAccentColor(accentColor: Long): Builder { this.accentColor = accentColor this.isCustomAccentColorApplied = true return this @@ -314,7 +309,9 @@ class PickVisualMediaRequest internal constructor() { * @return This builder. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) - fun setMediaCapabilitiesForTranscoding(mediaCapabilities: MediaCapabilities?): Builder { + public fun setMediaCapabilitiesForTranscoding( + mediaCapabilities: MediaCapabilities? + ): Builder { this.mediaCapabilitiesForTranscoding = mediaCapabilities return this } @@ -324,7 +321,7 @@ class PickVisualMediaRequest internal constructor() { * * @return the newly constructed PickVisualMediaRequest. */ - fun build(): PickVisualMediaRequest = + public fun build(): PickVisualMediaRequest = PickVisualMediaRequest().apply { this.mediaType = this@Builder.mediaType this.maxItems = this@Builder.maxItems diff --git a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContract.kt b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContract.kt index 41011f0907989..e3d98bd91261e 100644 --- a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContract.kt +++ b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContract.kt @@ -26,12 +26,12 @@ import android.content.Intent * * @see androidx.activity.result.ActivityResultCaller */ -abstract class ActivityResultContract { +public abstract class ActivityResultContract { /** Create an intent that can be used for [android.app.Activity.startActivityForResult]. */ - abstract fun createIntent(context: Context, input: I): Intent + public abstract fun createIntent(context: Context, input: I): Intent /** Convert result obtained from [android.app.Activity.onActivityResult] to [O]. */ - abstract fun parseResult(resultCode: Int, intent: Intent?): O + public abstract fun parseResult(resultCode: Int, intent: Intent?): O /** * An optional method you can implement that can be used to potentially provide a result in lieu @@ -40,7 +40,7 @@ abstract class ActivityResultContract { * @return the result wrapped in a [SynchronousResult] or `null` if the call should proceed to * start an activity. */ - open fun getSynchronousResult(context: Context, input: I): SynchronousResult? { + public open fun getSynchronousResult(context: Context, input: I): SynchronousResult? { return null } @@ -48,5 +48,5 @@ abstract class ActivityResultContract { * The wrapper for a result provided in [getSynchronousResult]. This allows differentiating * between a null [T] synchronous result and no synchronous result at all. */ - class SynchronousResult(val value: T) + public class SynchronousResult(public val value: T) } diff --git a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt index e67722fd1f18b..b2ade85f32639 100644 --- a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt +++ b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt @@ -54,7 +54,7 @@ import androidx.core.content.ContextCompat import kotlin.math.min /** A collection of some standard activity call contracts, as provided by android. */ -class ActivityResultContracts private constructor() { +public class ActivityResultContracts private constructor() { /** * An [ActivityResultContract] that doesn't do any type conversion, taking raw [Intent] as an * input and [ActivityResult] as an output. @@ -63,9 +63,9 @@ class ActivityResultContracts private constructor() { * avoid having to manage request codes when calling an activity API for which a type-safe * contract is not available. */ - class StartActivityForResult : ActivityResultContract() { + public class StartActivityForResult : ActivityResultContract() { - companion object { + public companion object { /** * Key for the extra containing a [android.os.Bundle] generated from * [androidx.core.app.ActivityOptionsCompat.toBundle] or @@ -74,7 +74,7 @@ class ActivityResultContracts private constructor() { * This will override any [androidx.core.app.ActivityOptionsCompat] passed to * [androidx.activity.result.ActivityResultLauncher.launch] */ - const val EXTRA_ACTIVITY_OPTIONS_BUNDLE = + public const val EXTRA_ACTIVITY_OPTIONS_BUNDLE: String = "androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE" } @@ -97,15 +97,15 @@ class ActivityResultContracts private constructor() { * of [ACTION_INTENT_SENDER_REQUEST] and an extra [EXTRA_SEND_INTENT_EXCEPTION] that contains * the thrown exception. */ - class StartIntentSenderForResult : + public class StartIntentSenderForResult : ActivityResultContract() { - companion object { + public companion object { /** * An [Intent] action for making a request via the [Activity.startIntentSenderForResult] * API. */ - const val ACTION_INTENT_SENDER_REQUEST = + public const val ACTION_INTENT_SENDER_REQUEST: String = "androidx.activity.result.contract.action.INTENT_SENDER_REQUEST" /** @@ -113,14 +113,14 @@ class ActivityResultContracts private constructor() { * * @see ACTION_INTENT_SENDER_REQUEST */ - const val EXTRA_INTENT_SENDER_REQUEST = + public const val EXTRA_INTENT_SENDER_REQUEST: String = "androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST" /** * Key for the extra containing the [android.content.IntentSender.SendIntentException] * if the call to [Activity.startIntentSenderForResult] fails. */ - const val EXTRA_SEND_INTENT_EXCEPTION = + public const val EXTRA_SEND_INTENT_EXCEPTION: String = "androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION" } @@ -133,10 +133,10 @@ class ActivityResultContracts private constructor() { } /** An [ActivityResultContract] to [request permissions][Activity.requestPermissions] */ - class RequestMultiplePermissions : + public class RequestMultiplePermissions : ActivityResultContract, Map>() { - companion object { + public companion object { /** * An [Intent] action for making a permission request via a regular * [Activity.startActivityForResult] API. @@ -150,7 +150,7 @@ class ActivityResultContracts private constructor() { * @see Activity.requestPermissions * @see Activity.onRequestPermissionsResult */ - const val ACTION_REQUEST_PERMISSIONS = + public const val ACTION_REQUEST_PERMISSIONS: String = "androidx.activity.result.contract.action.REQUEST_PERMISSIONS" /** @@ -158,14 +158,15 @@ class ActivityResultContracts private constructor() { * * @see ACTION_REQUEST_PERMISSIONS */ - const val EXTRA_PERMISSIONS = "androidx.activity.result.contract.extra.PERMISSIONS" + public const val EXTRA_PERMISSIONS: String = + "androidx.activity.result.contract.extra.PERMISSIONS" /** * Key for the extra containing whether permissions were granted. * * @see ACTION_REQUEST_PERMISSIONS */ - const val EXTRA_PERMISSION_GRANT_RESULTS = + public const val EXTRA_PERMISSION_GRANT_RESULTS: String = "androidx.activity.result.contract.extra.PERMISSION_GRANT_RESULTS" internal fun createIntent(input: Array): Intent { @@ -207,7 +208,7 @@ class ActivityResultContracts private constructor() { } /** An [ActivityResultContract] to [request a permission][Activity.requestPermissions] */ - class RequestPermission : ActivityResultContract() { + public class RequestPermission : ActivityResultContract() { override fun createIntent(context: Context, input: String): Intent { return RequestMultiplePermissions.createIntent(arrayOf(input)) } @@ -244,7 +245,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class TakePicturePreview : ActivityResultContract() { + public open class TakePicturePreview : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Void?): Intent { return Intent(MediaStore.ACTION_IMAGE_CAPTURE) @@ -270,7 +271,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class TakePicture : ActivityResultContract() { + public open class TakePicture : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Uri): Intent { return Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, input) @@ -300,7 +301,7 @@ class ActivityResultContracts private constructor() { """The thumbnail bitmap is rarely returned and is not a good signal to determine whether the video was actually successfully captured. Use {@link CaptureVideo} instead.""" ) - open class TakeVideo : ActivityResultContract() { + public open class TakeVideo : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Uri): Intent { return Intent(MediaStore.ACTION_VIDEO_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, input) @@ -326,7 +327,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class CaptureVideo : ActivityResultContract() { + public open class CaptureVideo : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Uri): Intent { return Intent(MediaStore.ACTION_VIDEO_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, input) @@ -350,7 +351,7 @@ class ActivityResultContracts private constructor() { * * @see ContactsContract */ - class PickContact : ActivityResultContract() { + public class PickContact : ActivityResultContract() { override fun createIntent(context: Context, input: Void?): Intent { return Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE) } @@ -371,7 +372,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class GetContent : ActivityResultContract() { + public open class GetContent : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: String): Intent { return Intent(Intent.ACTION_GET_CONTENT) @@ -400,7 +401,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class GetMultipleContents : + public open class GetMultipleContents : ActivityResultContract>() { @CallSuper override fun createIntent(context: Context, input: String): Intent { @@ -453,7 +454,7 @@ class ActivityResultContracts private constructor() { * * @see DocumentsContract */ - open class OpenDocument : ActivityResultContract, Uri?>() { + public open class OpenDocument : ActivityResultContract, Uri?>() { @CallSuper override fun createIntent(context: Context, input: Array): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT) @@ -482,7 +483,7 @@ class ActivityResultContracts private constructor() { * * @see DocumentsContract */ - open class OpenMultipleDocuments : + public open class OpenMultipleDocuments : ActivityResultContract, List<@JvmSuppressWildcards Uri>>() { @CallSuper override fun createIntent(context: Context, input: Array): Intent { @@ -516,7 +517,7 @@ class ActivityResultContracts private constructor() { * @see DocumentsContract.buildDocumentUriUsingTree * @see DocumentsContract.buildChildDocumentsUriUsingTree */ - open class OpenDocumentTree : ActivityResultContract() { + public open class OpenDocumentTree : ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Uri?): Intent { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) @@ -545,7 +546,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class CreateDocument(private val mimeType: String) : + public open class CreateDocument(private val mimeType: String) : ActivityResultContract() { @Deprecated( @@ -555,7 +556,7 @@ class ActivityResultContracts private constructor() { "CreateDocument(\"image/png\")).", ReplaceWith("CreateDocument(\"todo/todo\")"), ) - constructor() : this("*/*") + public constructor() : this("*/*") @CallSuper override fun createIntent(context: Context, input: String): Intent { @@ -599,8 +600,8 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class PickVisualMedia : ActivityResultContract() { - companion object { + public open class PickVisualMedia : ActivityResultContract() { + public companion object { /** * Check if the current device has support for the photo picker by checking the running * Android version or the SDK extension version. @@ -616,7 +617,7 @@ class ActivityResultContracts private constructor() { replaceWith = ReplaceWith("isPhotoPickerAvailable(context)"), ) @JvmStatic - fun isPhotoPickerAvailable(): Boolean { + public fun isPhotoPickerAvailable(): Boolean { return isSystemPickerAvailable() } @@ -633,7 +634,7 @@ class ActivityResultContracts private constructor() { * by [createIntent] to create the correct Intent for the current device. */ @field:Suppress("ActionValue") /* Don't include SYSTEM_FALLBACK in the action */ - const val ACTION_SYSTEM_FALLBACK_PICK_IMAGES = + public const val ACTION_SYSTEM_FALLBACK_PICK_IMAGES: String = "androidx.activity.result.contract.action.PICK_IMAGES" internal const val GMS_ACTION_PICK_IMAGES = @@ -652,7 +653,7 @@ class ActivityResultContracts private constructor() { * enforced. */ @field:Suppress("ActionValue") /* Don't include SYSTEM_FALLBACK in the extra */ - const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX = + public const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX: String = "androidx.activity.result.contract.extra.PICK_IMAGES_MAX" /** @@ -664,7 +665,7 @@ class ActivityResultContracts private constructor() { */ @field:Suppress("ActionValue") /* Don't include SYSTEM_FALLBACK in the extra */ - const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_LAUNCH_TAB = + public const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_LAUNCH_TAB: String = "androidx.activity.result.contract.extra.PICK_IMAGES_LAUNCH_TAB" /** @@ -674,7 +675,7 @@ class ActivityResultContracts private constructor() { */ @field:Suppress("ActionValue") /* Don't include SYSTEM_FALLBACK in the extra */ - const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_IN_ORDER = + public const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_IN_ORDER: String = "androidx.activity.result.contract.extra.PICK_IMAGES_IN_ORDER" /** @@ -686,7 +687,7 @@ class ActivityResultContracts private constructor() { */ @field:Suppress("ActionValue") /* Don't include SYSTEM_FALLBACK in the extra */ - const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_ACCENT_COLOR = + public const val EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_ACCENT_COLOR: String = "androidx.activity.result.contract.extra.PICK_IMAGES_ACCENT_COLOR" /** @@ -696,7 +697,7 @@ class ActivityResultContracts private constructor() { */ @SuppressLint("NewApi") @JvmStatic - fun isPhotoPickerAvailable(context: Context): Boolean { + public fun isPhotoPickerAvailable(context: Context): Boolean { return isSystemPickerAvailable() || isSystemFallbackPickerAvailable(context) } @@ -746,22 +747,22 @@ class ActivityResultContracts private constructor() { } /** Represents filter input type accepted by the photo picker. */ - sealed interface VisualMediaType + public sealed interface VisualMediaType /** [VisualMediaType] object used to filter images only when using the photo picker. */ - object ImageOnly : VisualMediaType + public object ImageOnly : VisualMediaType /** [VisualMediaType] object used to filter video only when using the photo picker. */ - object VideoOnly : VisualMediaType + public object VideoOnly : VisualMediaType /** [VisualMediaType] object used to filter images and video when using the photo picker. */ - object ImageAndVideo : VisualMediaType + public object ImageAndVideo : VisualMediaType /** * [VisualMediaType] class used to filter a single mime type only when using the photo * picker. */ - class SingleMimeType(val mimeType: String) : VisualMediaType + public class SingleMimeType(public val mimeType: String) : VisualMediaType /** * Represents the media capabilities of an application. @@ -773,9 +774,9 @@ class ActivityResultContracts private constructor() { * * @see PickVisualMediaRequest.Builder.setMediaCapabilitiesForTranscoding */ - class MediaCapabilities internal constructor() { + public class MediaCapabilities internal constructor() { - companion object { + public companion object { /** Defines the type of HDR (high dynamic range). */ @Retention(AnnotationRetention.SOURCE) @IntDef(TYPE_HLG10, TYPE_HDR10, TYPE_HDR10_PLUS, TYPE_DOLBY_VISION) @@ -785,19 +786,19 @@ class ActivityResultContracts private constructor() { AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER, ) - annotation class HdrType + public annotation class HdrType /** HDR type for HLG10. */ - const val TYPE_HLG10 = 0 + public const val TYPE_HLG10: Int = 0 /** HDR type for HDR10. */ - const val TYPE_HDR10 = 1 + public const val TYPE_HDR10: Int = 1 /** HDR type for HDR10+. */ - const val TYPE_HDR10_PLUS = 2 + public const val TYPE_HDR10_PLUS: Int = 2 /** HDR type for Dolby-Vision. */ - const val TYPE_DOLBY_VISION = 3 + public const val TYPE_DOLBY_VISION: Int = 3 } - var supportedHdrTypes: Set<@HdrType Int> = emptySet() + public var supportedHdrTypes: Set<@HdrType Int> = emptySet() internal set @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -818,7 +819,7 @@ class ActivityResultContracts private constructor() { } /** A builder for constructing [MediaCapabilities] instances. */ - class Builder { + public class Builder { private var supportedHdrTypes: MutableSet<@HdrType Int> = mutableSetOf() @@ -829,7 +830,7 @@ class ActivityResultContracts private constructor() { * @return This Builder. * @throws IllegalArgumentException if an invalid hdrType is provided. */ - fun addSupportedHdrType(hdrType: @HdrType Int): Builder { + public fun addSupportedHdrType(hdrType: @HdrType Int): Builder { this.supportedHdrTypes.add(hdrType) return this } @@ -839,7 +840,7 @@ class ActivityResultContracts private constructor() { * * @return the newly constructed MediaCapabilities. */ - fun build(): MediaCapabilities = + public fun build(): MediaCapabilities = MediaCapabilities().apply { this.supportedHdrTypes = this@Builder.supportedHdrTypes } @@ -847,20 +848,20 @@ class ActivityResultContracts private constructor() { } /** Represents filter input type accepted by the photo picker. */ - abstract class DefaultTab private constructor() { - abstract val value: Int + public abstract class DefaultTab private constructor() { + public abstract val value: Int /** * [DefaultTab] object used to open the picker in Photos tab (also the default if no * value is provided). */ - object PhotosTab : DefaultTab() { - override val value = MediaStore.PICK_IMAGES_TAB_IMAGES + public object PhotosTab : DefaultTab() { + override val value: Int = MediaStore.PICK_IMAGES_TAB_IMAGES } /** [DefaultTab] object used to open the picker in Albums tab. */ - object AlbumsTab : DefaultTab() { - override val value = MediaStore.PICK_IMAGES_TAB_ALBUMS + public object AlbumsTab : DefaultTab() { + override val value: Int = MediaStore.PICK_IMAGES_TAB_ALBUMS } } @@ -962,7 +963,7 @@ class ActivityResultContracts private constructor() { * This can be extended to override [createIntent] if you wish to pass additional extras to the * Intent created by `super.createIntent()`. */ - open class PickMultipleVisualMedia(private val maxItems: Int = getMaxItems()) : + public open class PickMultipleVisualMedia(private val maxItems: Int = getMaxItems()) : ActivityResultContract>() { init { diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmMain/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityChecker.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmMain/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityChecker.kt index 5a6e4b0f96c0e..28edcd8c1b6c5 100644 --- a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmMain/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityChecker.kt +++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmMain/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityChecker.kt @@ -449,43 +449,50 @@ class BinaryCompatibilityChecker( shouldFreeze: Boolean = false, dependencies: Map> = mapOf(), ): List { - val errors = CompatibilityErrors(baselines, "meta") + val targetErrors = CompatibilityErrors(baselines, "meta") val removedTargets = oldLibraries.keys - newLibraries.keys val addedTargets = newLibraries.keys - oldLibraries.keys if (removedTargets.isNotEmpty()) { - errors.addAll( + targetErrors.addAll( removedTargets.flatMap { CompatibilityErrors(baselines, it).apply { add("Target was removed") } } ) } if (shouldFreeze && addedTargets.isNotEmpty()) { - errors.addAll( + targetErrors.addAll( addedTargets.flatMap { CompatibilityErrors(baselines, it).apply { add("Target was added") } } ) } - if (errors.isNotEmpty()) { - if (validate) { - throw ValidationException(errors.toString()) + val compatErrors = + oldLibraries.keys.flatMap { target -> + val newLib = + newLibraries[target] + // We can't compare targets if they've been removed. We'll throw on + // removed + // targets but if that removal is baselined we can still make it here. + ?: return@flatMap emptyList() + val oldLib = oldLibraries[target]!! + val errorsForTarget = CompatibilityErrors(baselines, target) + val dependenciesForTarget = + dependencies.getOrElse(target) { + throw IllegalStateException("Dependencies missing for target $target") + } + BinaryCompatibilityChecker(newLib, oldLib, dependenciesForTarget) + .checkBinariesAreCompatible( + errors = errorsForTarget, + // don't throw on the individual target, we'll throw all errors after + // once we've collected them + validate = false, + shouldFreeze = shouldFreeze, + ) + } + return (targetErrors + compatErrors).also { + if (validate && it.isNotEmpty()) { + throw ValidationException(it.toString()) } - return errors - } - return oldLibraries.keys.flatMap { target -> - val newLib = - newLibraries[target] - // We can't compare targets if they've been removed. We'll throw on removed - // targets but if that removal is baselined we can still make it here. - ?: return@flatMap emptyList() - val oldLib = oldLibraries[target]!! - val errorsForTarget = CompatibilityErrors(baselines, target) - val dependenciesForTarget = - dependencies.getOrElse(target) { - throw IllegalStateException("Dependencies missing for target $target") - } - BinaryCompatibilityChecker(newLib, oldLib, dependenciesForTarget) - .checkBinariesAreCompatible(errorsForTarget, validate, shouldFreeze) } } diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmTest/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityCheckerTest.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmTest/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityCheckerTest.kt index 02d1174687a83..9c3105ffd1d60 100644 --- a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmTest/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityCheckerTest.kt +++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/jvmTest/kotlin/androidx/binarycompatibilityvalidator/BinaryCompatibilityCheckerTest.kt @@ -71,6 +71,7 @@ class BinaryCompatibilityCheckerTest { KlibDumpParser(currentDump).parse(), KlibDumpParser(previousDump).parse(), shouldFreeze = false, + dependencies = mapOf("linux" to emptySet(), "ios" to emptySet()), ) } assertThat(e.message).contains("[ios]: Target was removed") @@ -942,7 +943,7 @@ class BinaryCompatibilityCheckerTest { } @Test - fun removeConcreteImplemantation() { + fun removeConcreteImplementation() { val beforeText = """ abstract class my.lib/MyClass : my.lib/MyInterface { // my.lib/MyClass|null[0] @@ -1646,7 +1647,11 @@ class BinaryCompatibilityCheckerTest { val e = assertFailsWith { - BinaryCompatibilityChecker.checkAllBinariesAreCompatible(afterLibs, beforeLibs) + BinaryCompatibilityChecker.checkAllBinariesAreCompatible( + afterLibs, + beforeLibs, + dependencies = mapOf("iosX64" to emptySet(), "linuxX64" to emptySet()), + ) } assertThat(e.message).contains("[iosX64]: Target was removed") } @@ -1666,6 +1671,53 @@ class BinaryCompatibilityCheckerTest { ) } + @Test + fun removedDeclarationsAndTargetsAreBothReported() { + val beforeText = + """ + // KLib ABI Dump + // Targets: [linuxX64, iosX64] + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: true + // - Show declarations: true + // Library unique name: + final class my.lib/MyClass { // my.lib/MyClass|null[0] + constructor () // my.lib/MyClass.|(){}[0] + final fun myFun(): kotlin/String // my.lib/MyClass.myFun|myFun(){}[0] + } + """ + val afterText = + """ + // KLib ABI Dump + // Targets: [linuxX64] + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: true + // - Show declarations: true + // Library unique name: + final class my.lib/MyClass { // my.lib/MyClass|null[0] + constructor () // my.lib/MyClass.|(){}[0] + } + """ + val beforeLibs = KlibDumpParser(beforeText).parse() + val afterLibs = KlibDumpParser(afterText).parse() + val errors = + BinaryCompatibilityChecker.checkAllBinariesAreCompatible( + afterLibs, + beforeLibs, + baselines = emptySet(), + shouldFreeze = false, + validate = false, + dependencies = mapOf("iosX64" to setOf(stdlibKlib), "linuxX64" to setOf(stdlibKlib)), + ) + assertThat(errors.map { it.toString() }) + .containsExactly( + "[iosX64]: Target was removed", + "[linuxX64]: Removed declaration myFun() from my.lib/MyClass", + ) + } + @Test fun returnsErrorsObjectWhenValidateIsFalse() { val beforeText = diff --git a/buildSrc-tests/src/test/java/androidx/build/ProjectCreatorTaskTest.kt b/buildSrc-tests/src/test/java/androidx/build/ProjectCreatorTaskTest.kt index 48f934ca46f54..cb1f029bb12ac 100644 --- a/buildSrc-tests/src/test/java/androidx/build/ProjectCreatorTaskTest.kt +++ b/buildSrc-tests/src/test/java/androidx/build/ProjectCreatorTaskTest.kt @@ -16,12 +16,191 @@ package androidx.build -import androidx.testutils.assertThrows +import androidx.testutils.gradle.ProjectSetupRule +import com.google.common.truth.Truth.assertThat +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertTrue +import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder class ProjectCreatorTaskTest { + @get:Rule val projectSetup = ProjectSetupRule() + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun testIsGroupIdAtomic() { + val file = tempFolder.newFile() + file.writeText( + """ + [versions] + BAR = "1.0.0-alpha01" + FOO = "1.0.0-alpha01" + + [groups] + BAR = { group = "androidx.bar" } + FOO = { group = "androidx.foo", atomicGroupVersion = "versions.FOO" } + """ + .trimIndent() + ) + + val projectSpecBar = + ProjectSpec( + "androidx.bar", + "bar-foo", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + assertFalse(VersionCatalogEditor(file, projectSpecBar).isGroupIdAtomic()) + + val projectSpecFoo = + ProjectSpec( + "androidx.foo", + "foo-abc", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + assertTrue(VersionCatalogEditor(file, projectSpecFoo).isGroupIdAtomic()) + } + + @Test + fun testUpdateLibraryVersionsToml() { + val file = tempFolder.newFile() + file.writeText( + """ + [versions] + FOO = "1.0.0-alpha01" + + [groups] + FOO = { group = "androidx.foo", atomicGroupVersion = "versions.FOO" } + """ + .trimIndent() + ) + + val projectSpec = + ProjectSpec( + "androidx.bar", + "bar-foo", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + val catalogEditor = VersionCatalogEditor(file, projectSpec) + catalogEditor.updateLibraryVersionsToml() + assertThat(file.readText()) + .isEqualTo( + """ + [versions] + BAR = "1.0.0-alpha01" + FOO = "1.0.0-alpha01" + + [groups] + BAR = { group = "androidx.bar", atomicGroupVersion = "versions.BAR" } + FOO = { group = "androidx.foo", atomicGroupVersion = "versions.FOO" } + + """ + .trimIndent() + ) + } + + @Test + fun testUpdateSettingsGradleFile() { + val file = tempFolder.newFile() + file.writeText( + """ + // Stuff before includeProject section + class MyClass { + } + // End stuff before includeProject section + + includeProject(":activity:activity", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE]) + """ + .trimIndent() + ) + val gradleSettingsEditor = GradleSettingsEditor(file) + val projectSpec = + ProjectSpec( + "androidx.foo", + "foo-bar", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + gradleSettingsEditor.updateSettingsGradle(projectSpec) + assertThat(file.readText()) + .isEqualTo( + """ + // Stuff before includeProject section + class MyClass { + } + // End stuff before includeProject section + + includeProject(":activity:activity", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE]) + includeProject(":foo:foo-bar", [BuildType.MAIN]) + + """ + .trimIndent() + ) + } + + @Test + fun validateName_valid() { + ProjectSpec( + "androidx.foo", + "foo-bar", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + } + + @Test(expected = IllegalArgumentException::class) + fun validateName_invalidGroupId() { + ProjectSpec("com.example", "foo-bar", ProjectType.ANDROID_LIBRARY, "", projectSetup.rootDir) + } + + @Test(expected = IllegalArgumentException::class) + fun validateName_invalidArtifactId() { + ProjectSpec( + "androidx.foo", + "baz-bar", + ProjectType.ANDROID_LIBRARY, + "", + projectSetup.rootDir, + ) + } + + @Test + fun testGeneratePackageName() { + assertThat(generatePackageName("androidx.foo", "foo-bar")).isEqualTo("androidx.foo.bar") + assertThat(generatePackageName("androidx.foo.bar", "bar-qux")) + .isEqualTo("androidx.foo.bar.qux") + assertThat(generatePackageName("androidx.foo", "foo")).isEqualTo("androidx.foo") + } + + @Test + fun testGetGroupIdVersionMacro() { + assertThat(getGroupIdVersionMacro("androidx.foo")).isEqualTo("FOO") + assertThat(getGroupIdVersionMacro("androidx.foo.bar")).isEqualTo("FOO_BAR") + assertThat(getGroupIdVersionMacro("androidx.compose.ui")).isEqualTo("COMPOSE_UI") + assertThat(getGroupIdVersionMacro("androidx.compose")).isEqualTo("COMPOSE") + } + + @Test + fun getGradleProjectCoordinates() { + assertThat(getGradleProjectCoordinates("androidx.foo", "foo-bar")).isEqualTo(":foo:foo-bar") + assertThat(getGradleProjectCoordinates("androidx.foo.bar", "bar-qux")) + .isEqualTo(":foo:bar:bar-qux") + } + @Test - fun basicTest() { - assertThrows(IllegalStateException::class.java) { runProjectCreatorTask() } + fun getLibraryType() { + assertThat(getLibraryType("foo-sample")).isEqualTo("SAMPLES") + assertThat(getLibraryType("foo-compiler")).isEqualTo("ANNOTATION_PROCESSOR") + assertThat(getLibraryType("foo-lint")).isEqualTo("LINT") + assertThat(getLibraryType("foo-inspection")).isEqualTo("IDE_PLUGIN") + assertThat(getLibraryType("foo-bar")).isEqualTo("PUBLISHED_LIBRARY") } } diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectCreatorTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectCreatorTask.kt index 294c4b0157513..49ece0c49e2e3 100644 --- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectCreatorTask.kt +++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectCreatorTask.kt @@ -14,20 +14,487 @@ * limitations under the License. */ +// This suppression is for usage of UserInputHandler and UserQuestions (from Gradle). +// For more information, see https://github.com/gradle/gradle/issues/28216 +@file:Suppress("InternalGradleApiUsage") + package androidx.build +import com.google.common.annotations.VisibleForTesting +import java.io.File +import java.time.LocalDate import org.gradle.api.DefaultTask +import org.gradle.api.internal.tasks.userinput.UserInputHandler +import org.gradle.api.internal.tasks.userinput.UserQuestions import org.gradle.api.tasks.TaskAction import org.gradle.work.DisableCachingByDefault +import org.tomlj.Toml +import org.tomlj.TomlParseResult +import org.tomlj.TomlTable -@DisableCachingByDefault(because = "Doesn't benefit from caching") +@DisableCachingByDefault(because = "Interactive task, must run every time") abstract class ProjectCreatorTask : DefaultTask() { + private val supportDir = project.getSupportRootFolder() + private val currentlySupportedProjectTypes = listOf(ProjectType.ANDROID_LIBRARY) + @TaskAction fun exec() { - runProjectCreatorTask() + val spec: ProjectSpec = promptForProjectSpec() + val catalogEditor = VersionCatalogEditor(File(supportDir, "libraryversions.toml"), spec) + val settingsEditor = GradleSettingsEditor(File(supportDir, "settings.gradle")) + val docsTotBuildGradleEditor = + DocsTotBuildGradleEditor(File(supportDir, "docs-tip-of-tree/build.gradle")) + val projectGenerator = ProjectGenerator() + + catalogEditor.updateLibraryVersionsToml() + settingsEditor.updateSettingsGradle(spec) + docsTotBuildGradleEditor.updateDocsTotBuildGradle(spec) + projectGenerator.createDirectories(spec, catalogEditor.isGroupIdAtomic()) + + printTodoList(spec) + } + + private fun promptForProjectSpec(): ProjectSpec { + val userInput = services.get(UserInputHandler::class.java) + + val groupId = + userInput + .askUser { interaction: UserQuestions -> + interaction.askQuestion("Enter group id (e.g. androidx.core)", "none") + } + .get() + val artifactId = + userInput + .askUser { interaction: UserQuestions -> + interaction.askQuestion("Enter artifact id (e.g. core-telecom)", "none") + } + .get() + + val projectTypeName = + userInput + .askUser { interaction: UserQuestions -> + interaction.selectOption( + "Please choose the type of project you would like to create", + ProjectType.entries.map { it.description }, + ProjectType.ANDROID_LIBRARY.description, + ) + } + .get() + val projectDescription = + userInput + .askUser { interaction: UserQuestions -> + interaction.askQuestion("Enter project description", "") + } + .get() + + val projectType = ProjectType.entries.find { it.description == projectTypeName } + + // This is a temporary check that will be removed once all project types are supported + if (projectType == null || projectType !in currentlySupportedProjectTypes) { + error("Project type not yet supported") + } + + return ProjectSpec(groupId, artifactId, projectType, projectDescription, supportDir) + } + + private fun printTodoList(projectSpec: ProjectSpec) { + projectSpec.fullArtifactPath + val buildGradlePath = projectSpec.fullArtifactPath.resolve("build.gradle") + val ownersFilePath = projectSpec.fullArtifactPath.resolve("OWNERS") + val packageDocsPath = + getPackageDocumentationFileDir(projectSpec) + .resolve( + getPackageDocumentationFilename( + projectSpec.groupIdWithPrefix, + projectSpec.artifactId, + projectSpec.projectType, + ) + ) + + println( + """ + --- + Created the project. The following TODOs need to be completed by you: + + 1. Check that the OWNERS file is in the correct place. It is currently at: + ${ownersFilePath.path} + 2. Add your name (and others) to the OWNERS file: + ${ownersFilePath.path} + 3. Check that the correct library version is assigned in the build.gradle: + ${buildGradlePath.path} + 4. Fill out the project/module name in the build.gradle: + ${buildGradlePath.path} + 5. Update the project/module package documentation: + ${packageDocsPath.path} + """ + .trimIndent() + ) + } +} + +@VisibleForTesting +internal class GradleSettingsEditor(val settingsGradleFile: File) { + fun updateSettingsGradle(spec: ProjectSpec) { + val settingsLines = settingsGradleFile.readLines().toMutableList() + val newLine = getNewSettingsGradleLine(spec.groupId, spec.artifactId) + + val insertLine = + settingsLines.indexOfFirst { it.contains("includeProject") && it > newLine } + if (insertLine != -1) { + settingsLines.add(insertLine, newLine) + } else { + settingsLines.add(newLine) + } + + settingsGradleFile.writeText(settingsLines.joinToString("\n") + "\n") + } + + private fun getNewSettingsGradleLine(groupId: String, artifactId: String): String { + val buildType = if (isComposeProject(groupId, artifactId)) "COMPOSE" else "MAIN" + val gradlePath = getGradleProjectCoordinates(groupId, artifactId) + return "includeProject(\"$gradlePath\", [BuildType.$buildType])" + } +} + +@VisibleForTesting +internal class VersionCatalogEditor(val tomlFile: File, val spec: ProjectSpec) { + + /** + * Checks if a group ID is atomic using the libraryversions.toml file. + * + * If one already exists, then this function evaluates the group id and returns the appropriate + * atomicity. Otherwise, it returns False. + * + * Example of an atomic library group: ACTIVITY = { group = "androidx.work", atomicGroupVersion + * = "WORK" } Example of a non-atomic library group: WEAR = { group = "androidx.wear" } + */ + fun isGroupIdAtomic(): Boolean { + val tomlParseResult: TomlParseResult = Toml.parse(tomlFile.toPath()) + val groupsTable = tomlParseResult.getTable("groups") ?: return false + val groupEntry = groupsTable.getTable(getGroupIdVersionMacro(spec.groupId)) + return groupEntry?.contains("atomicGroupVersion") == true + } + + fun updateLibraryVersionsToml() { + val tomlLines = tomlFile.readLines().toMutableList() + val tomlParseResult: TomlParseResult = Toml.parse(tomlFile.toPath()) + + registerVersion(tomlLines, tomlParseResult, spec.groupId) + registerGroup(tomlLines, tomlParseResult, spec.groupIdWithPrefix) + + tomlFile.writeText(tomlLines.joinToString("\n", postfix = "\n")) + } + + private fun registerVersion( + tomlLines: MutableList, + parseResult: TomlParseResult, + groupId: String, + ) { + // Update [versions] section + + val groupIdVersionMacro = getGroupIdVersionMacro(groupId) + + val versionsTable: TomlTable? = parseResult.getTable("versions") + val versionExists = versionsTable?.contains(groupIdVersionMacro) == true + + if (!versionExists) { + val versionsBlockStart = tomlLines.indexOf("[versions]") + val groupsBlockStart = tomlLines.indexOf("[groups]") + + val newVersionLine = "$groupIdVersionMacro = \"1.0.0-alpha01\"" + var versionInsertIndex = groupsBlockStart // Default insert point + + // Find the correct alphabetical insertion index within the [versions] block + for (i in versionsBlockStart + 1 until groupsBlockStart) { + val line = tomlLines[i].trim() + if (line.isEmpty() || line.startsWith("#") || line.startsWith("[")) continue + if (line > newVersionLine) { + versionInsertIndex = i + break + } + } + tomlLines.add(versionInsertIndex, newVersionLine) + println("Added version entry for '$groupIdVersionMacro' in libraryversions.toml.") + } else { + println( + "Version entry for '$groupIdVersionMacro' already exists in libraryversions.toml. Skipping." + ) + } + } + + private fun registerGroup( + tomlLines: MutableList, + parseResult: TomlParseResult, + groupId: String, + ) { + // update [groups] section + + val groupIdVersionMacro = getGroupIdVersionMacro(groupId) + + // Re-find groupsBlockStart as tomlLines might have been modified + val newGroupsBlockStart = tomlLines.indexOf("[groups]") + + val groupsTable: TomlTable? = parseResult.getTable("groups") + // Check if any key within [groups] has a sub-table with 'group = "$groupId"' + val groupExists = + groupsTable?.keySet()?.any { key -> + val groupSpec = groupsTable.getTable(key) + groupSpec?.getString("group") == groupId + } == true + + if (!groupExists) { + val newGroupLine = + """$groupIdVersionMacro = { group = "$groupId", atomicGroupVersion = "versions.$groupIdVersionMacro" }""" + var groupInsertIndex = tomlLines.size // Default insert at the end + + // Find the correct alphabetical insertion index within the [groups] block + for (i in newGroupsBlockStart + 1 until tomlLines.size) { + val line = tomlLines[i].trim() + if (line.isEmpty() || line.startsWith("#") || line.startsWith("[")) continue + if (line > newGroupLine) { + groupInsertIndex = i + break + } + } + tomlLines.add(groupInsertIndex, newGroupLine) + println("Added group entry for '$groupId' in libraryversions.toml.") + } else { + println("Group entry for '$groupId' already exists in libraryversions.toml. Skipping.") + } } } -fun runProjectCreatorTask() { - error("This task is under development") +internal class DocsTotBuildGradleEditor(val docsTotBuildGradleFile: File) { + fun updateDocsTotBuildGradle(spec: ProjectSpec) { + if ( + ("test" in spec.groupId || + "test" in spec.artifactId || + "benchmark" in spec.groupId || + "benchmark" in spec.artifactId) + ) { + println( + "Skipping docs-tip-of-tree update for test/benchmark library " + + "$spec.groupId:$spec.artifactId. Please add manually if needed." + ) + return + } + + val newLine = getNewDocsTotBuildGradleLine(spec.groupId, spec.artifactId) ?: return + val docLines = docsTotBuildGradleFile.readLines().toMutableList() + + val dependenciesBlockStart = + docLines.indexOfFirst { it.trim().startsWith("dependencies {") } + if (dependenciesBlockStart == -1) { + error("Error: Could not find 'dependencies {' block in " + docsTotBuildGradleFile.path) + } + + val newProjectPart = newLine.split("project")[1] + val insertLine = + docLines.indexOfFirst { + it.contains("project") && it.substringAfter("project") >= newProjectPart + } + + if (insertLine != -1) { + docLines.add(insertLine, newLine) + } else { + docLines.add(dependenciesBlockStart + 1, newLine) + } + + docsTotBuildGradleFile.writeText(docLines.joinToString("\n", postfix = "\n")) + } + + private fun getNewDocsTotBuildGradleLine(groupId: String, artifactId: String): String? { + if ("sample" in artifactId) { + println( + "Auto-detected sample project. Please add the sample dependency to the " + + "androidx block of the library's build.gradle file." + ) + return null + } + val gradlePath = getGradleProjectCoordinates(groupId, artifactId) + return """ docs(project("$gradlePath"))""" + } +} + +@VisibleForTesting +internal class ProjectGenerator { + fun createDirectories(spec: ProjectSpec, isGroupIdAtomic: Boolean) { + spec.fullArtifactPath.mkdirs() + + // create documentation md file + val docFile = + File( + spec.fullArtifactPath, + "src/main/kotlin/androidx/${spec.groupId.replace(".", "/")}/androidx-${ + spec.groupId.replace( + ".", + "-", + ) + }-${spec.artifactId}-documentation.md", + ) + docFile.parentFile.mkdirs() + docFile.writeText(spec.toPackageDocsText()) + + // create OWNERS file + val ownersFile = File(spec.fullArtifactPath, "OWNERS") + ownersFile.writeText("# example@google.com\n") + + // create build.gradle file + val buildGradleFile = File(spec.fullArtifactPath, "build.gradle") + buildGradleFile.writeText(spec.getBuildGradleText(isGroupIdAtomic)) + + // Write current.txt, res-current.txt, and restricted_current.txt + for (signatureFileName: String in listOf("current", "res-current", "restricted_current")) { + val txtFile = File(spec.fullArtifactPath, "api/$signatureFileName.txt") + txtFile.parentFile.mkdirs() + txtFile.writeText( + if (signatureFileName != "res-current") "// Signature format: 4.0\n" else "" + ) + } + } + + private fun ProjectSpec.toPackageDocsText(): String { + return """ + # Module root + + $groupId $artifactId + + # Package ${generatePackageName(groupId, artifactId)} + + Insert package level documentation here + """ + .trimIndent() + } + + private fun ProjectSpec.getBuildGradleText(isGroupIdAtomic: Boolean): String { + return """ + /* + * Copyright (C) ${getYear()} 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. + */ + + /** + * This file was created using the `createProject` gradle task (./gradlew createProject) + * + * Please use the task when creating a new project, rather than copying an existing project and + * modifying its settings. + */ + import androidx.build.SoftwareType + + plugins { + id("AndroidXPlugin") + id("com.android.library") + } + + dependencies { + // Add dependencies here + } + + android { + namespace = "${generatePackageName(groupId, artifactId)}" + } + + androidx { + name = "${groupId}:${artifactId}" + type = SoftwareType.${getLibraryType(artifactId)} + ${if (isGroupIdAtomic) "" else "mavenVersion = LibraryVersions.${getGroupIdVersionMacro(groupId)}"} + inceptionYear = "${getYear()}" + description = "$description" + } + """ + .trimIndent() + } + + private fun getYear(): String = LocalDate.now().year.toString() +} + +private fun getPackageDocumentationFileDir(spec: ProjectSpec): File { + val subPath = + if (spec.projectType == ProjectType.ANDROID_LIBRARY) { + "src/main/kotlin/" + } else { + error("Project type not yet supported") + } + spec.groupIdWithPrefix.replace('.', '/') + return File(spec.fullArtifactPath, subPath) +} + +@VisibleForTesting +internal enum class ProjectType(val description: String) { + ANDROID_LIBRARY("Android (AAR)"), + KMP("KMP (All platforms) (AAR)"), + JAVA("Java (JAR)"), +} + +@VisibleForTesting +internal data class ProjectSpec( + val groupIdWithPrefix: String, + val artifactId: String, + val projectType: ProjectType, + val description: String, + val supportRoot: File, +) { + val groupId = groupIdWithPrefix.removePrefix("androidx.") + + val fullArtifactPath = File(supportRoot, groupId.replace('.', '/')).resolve(artifactId) + + init { + require(groupIdWithPrefix.startsWith("androidx.")) { + "Group ID must start with 'androidx.'" + } + val finalGroupWord = groupIdWithPrefix.substringAfterLast('.') + require(artifactId.startsWith(finalGroupWord)) { + "Artifact ID must start with the last segment of the Group ID ($finalGroupWord)." + } + } +} + +private fun isComposeProject(groupId: String, artifactId: String): Boolean = + "compose" in groupId || "compose" in artifactId + +internal fun generatePackageName(groupId: String, artifactId: String): String { + val groupLast = groupId.split('.').last() + + val suffix = artifactId.removePrefix(groupLast).replace('-', '.').trim('.') + + return if (suffix.isEmpty()) groupId else "$groupId.$suffix" +} + +internal fun getGroupIdVersionMacro(groupId: String): String { + return groupId.removePrefix("androidx.").replace(".", "_").uppercase() +} + +internal fun getGradleProjectCoordinates(groupId: String, artifactId: String): String { + return ":${groupId.removePrefix("androidx.").replace(".", ":")}:$artifactId" +} + +internal fun getLibraryType(artifactId: String): String = + when { + "sample" in artifactId -> "SAMPLES" + "compiler" in artifactId -> "ANNOTATION_PROCESSOR" + "lint" in artifactId -> "LINT" + "inspection" in artifactId -> "IDE_PLUGIN" + else -> "PUBLISHED_LIBRARY" + } + +internal fun getPackageDocumentationFilename( + groupId: String, + artifactId: String, + projectType: ProjectType, +): String { + return if (projectType == ProjectType.JAVA) { + "package-info.java" + } else { + "${groupId.replace('.', '-')}-$artifactId-documentation.md" + } } diff --git a/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/IgnoreAbiChangesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/IgnoreAbiChangesTask.kt index d2c9db435fabb..6ccbfb0db6c1a 100644 --- a/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/IgnoreAbiChangesTask.kt +++ b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/IgnoreAbiChangesTask.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package androidx.build.binarycompatibilityvalidator import androidx.binarycompatibilityvalidator.BinaryCompatibilityChecker @@ -48,24 +47,17 @@ import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader abstract class IgnoreAbiChangesTask @Inject constructor(@Internal protected val workerExecutor: WorkerExecutor) : DefaultTask() { - /** Text file from which API signatures will be read. */ @get:PathSensitive(PathSensitivity.RELATIVE) @get:InputFile abstract val previousApiDump: RegularFileProperty - @get:PathSensitive(PathSensitivity.RELATIVE) @get:InputFile abstract val currentApiDump: RegularFileProperty - @get:OutputFile abstract val ignoreFile: RegularFileProperty - @get:Classpath abstract val runtimeClasspath: ConfigurableFileCollection - @get:Input abstract var referenceVersion: Provider - @get:Input abstract var projectVersion: Provider - @get:Nested abstract val dependencies: ListProperty @TaskAction @@ -118,10 +110,12 @@ private abstract class IgnoreChangesWorker : WorkAction .map { it.toString() } .toSet() parameters.ignoreFile.get().asFile.apply { - if (!exists()) { - createNewFile() + if (ignoredErrors.isEmpty()) { + takeIf { exists() }?.delete() + } else { + takeUnless { exists() }?.createNewFile() + writeText(FORMAT_STRING + "\n" + ignoredErrors.joinToString("\n")) } - writeText(FORMAT_STRING + "\n" + ignoredErrors.joinToString("\n")) } } diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt index 162786f4f3a01..440a452df7f5f 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt @@ -209,6 +209,19 @@ public value class OutputStatus internal constructor(public val value: Int) { */ public val ERROR_OUTPUT_DROPPED: OutputStatus = OutputStatus(13) } + + override fun toString(): String { + return when (value) { + 0 -> "PENDING" + 1 -> "AVAILABLE" + 2 -> "UNAVAILABLE" + 10 -> "ERROR_OUTPUT_FAILED" + 11 -> "ERROR_OUTPUT_ABORTED" + 12 -> "ERROR_OUTPUT_MISSING" + 13 -> "ERROR_OUTPUT_DROPPED" + else -> "OutputStatus(value=$value)" + } + } } /** diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt index 983c97f9c2cd5..4bc33fc07ee07 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt @@ -17,6 +17,7 @@ package androidx.camera.camera2.pipe.config import android.content.Context +import android.hardware.camera2.CameraCharacteristics import androidx.camera.camera2.pipe.CameraBackend import androidx.camera.camera2.pipe.CameraBackends import androidx.camera.camera2.pipe.CameraContext @@ -30,6 +31,7 @@ import androidx.camera.camera2.pipe.Request import androidx.camera.camera2.pipe.RequestListeners import androidx.camera.camera2.pipe.StreamGraph import androidx.camera.camera2.pipe.SurfaceTracker +import androidx.camera.camera2.pipe.core.SystemClockOffsets import androidx.camera.camera2.pipe.core.Threads import androidx.camera.camera2.pipe.graph.CameraGraphImpl import androidx.camera.camera2.pipe.graph.GraphListener @@ -174,9 +176,22 @@ internal abstract class SharedCameraGraphModules { fun provideFrameDistributor( streamGraphImpl: StreamGraphImpl, frameCaptureQueue: FrameCaptureQueue, + cameraMetadata: CameraMetadata, + systemClockOffsets: SystemClockOffsets, ): FrameDistributor { - return FrameDistributor(streamGraphImpl.imageSourceMap, frameCaptureQueue) + val isCameraTimebaseRealtime = + (cameraMetadata[CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE] == + CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME) + + return FrameDistributor( + streamGraphImpl, + frameCaptureQueue, + isCameraTimebaseRealtime, + systemClockOffsets.realtimeNsToMonotonicNs, + ) } + + @CameraGraphScope @Provides fun provideSystemClockOffsets() = SystemClockOffsets.estimate() } } diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/SystemClockOffsets.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/SystemClockOffsets.kt new file mode 100644 index 0000000000000..18bbe58ba4f18 --- /dev/null +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/SystemClockOffsets.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2026 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.core + +import android.os.SystemClock + +/** + * Represents fixed, precomputed offsets for various Android system clocks and time sources. + * + * Generally everything in Camera operates in either REALTIME timestamps or MONOTONIC timestamps. + * Most of the time, this is fine, but there are subtle problems because the two values can skew + * over time and as the device sleeps. In general, while the camera is open, the device never sleeps + * and so it's fine to cache these values while the camera is open. However, these skews must be + * recomputed each time the process wakes up and the camera starts again. + * + * Additionally, which timebase the camera uses can change depending on what device it's running on. + * While not documented, most cameras will operate on MONOTONIC timestamps unless they are + * calibrated and report that the camera timestamps are "realtime". + * + * Source: + * https://cs.android.com/android/platform/superproject/main/+/main:frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp;l=404 + * - See Android's Camera3Device.cpp -> getMonoToBoottimeOffset + * - See Android's Thread.cpp -> adjustTimebaseOffset + */ +internal class SystemClockOffsets +private constructor(val realtimeNsToUtcMs: Long, val realtimeNsToMonotonicNs: Long) { + companion object { + private const val NS_PER_MS: Long = 1_000_000 + private const val NS_PER_MS_X_2: Long = NS_PER_MS * 2 + private const val MEASUREMENT_ITERATIONS: Int = 3 + + /** Estimate system clock offsets by sampling and measuring the clock differences. */ + @JvmStatic + fun estimate(): SystemClockOffsets { + val realtimeNsToUtcMs = estimateRealtimeNsToUtcMs() + val realtimeNsToMonotonicNs = estimateRealtimeNsToMonotonicNs() + return SystemClockOffsets(realtimeNsToUtcMs, realtimeNsToMonotonicNs) + } + + /** Create a set of fixed system clock offsets. */ + @JvmStatic + fun fixed(realtimeNsToUtcMs: Long, realtimeNsToMonotonicNs: Long): SystemClockOffsets = + SystemClockOffsets(realtimeNsToUtcMs, realtimeNsToMonotonicNs) + + @JvmStatic + private fun estimateRealtimeNsToUtcMs(): Long { + var realtimeNanosA: Long + var realtimeNanosB: Long + var utcMillis: Long + + // See class comment for a detailed description of this measurement approach. + var bestDeltaNanos = Long.MAX_VALUE + var offsetMillis: Long = 0 + for (i in 0..() val internalOutputConfigMap = mutableMapOf() diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameDistributor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameDistributor.kt index 3dd6f0a4b3212..fc5374ba1735f 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameDistributor.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameDistributor.kt @@ -16,17 +16,24 @@ package androidx.camera.camera2.pipe.internal +import android.hardware.HardwareBuffer +import android.os.Build +import androidx.camera.camera2.pipe.CameraStream import androidx.camera.camera2.pipe.CameraTimestamp import androidx.camera.camera2.pipe.Frame import androidx.camera.camera2.pipe.FrameCapture import androidx.camera.camera2.pipe.FrameInfo import androidx.camera.camera2.pipe.FrameNumber import androidx.camera.camera2.pipe.FrameReference +import androidx.camera.camera2.pipe.ImageSourceConfig import androidx.camera.camera2.pipe.OutputStatus +import androidx.camera.camera2.pipe.OutputStream import androidx.camera.camera2.pipe.Request import androidx.camera.camera2.pipe.RequestFailure import androidx.camera.camera2.pipe.RequestMetadata import androidx.camera.camera2.pipe.StreamId +import androidx.camera.camera2.pipe.core.Log +import androidx.camera.camera2.pipe.graph.StreamGraphImpl import androidx.camera.camera2.pipe.media.ClosingFinalizer import androidx.camera.camera2.pipe.media.ImageSource import androidx.camera.camera2.pipe.media.NoOpFinalizer @@ -52,8 +59,10 @@ import androidx.camera.camera2.pipe.media.OutputImage * that were previously started. */ internal class FrameDistributor( - imageSources: Map, + streamGraphImpl: StreamGraphImpl, private val frameCaptureQueue: FrameCaptureQueue, + isCameraTimebaseRealtime: Boolean, + realtimeToMonotonicOffsetNs: Long, ) : AutoCloseable, Request.Listener { /** * Listener to observe new [FrameReferences][FrameReference] as they are started by the camera. @@ -66,7 +75,11 @@ internal class FrameDistributor( fun onFrameStarted(frameReference: FrameReference) } - private val frameInfoDistributor = OutputDistributor(outputFinalizer = NoOpFinalizer) + private val frameInfoDistributor = + OutputDistributor( + outputFinalizer = NoOpFinalizer, + outputMatcher = OutputMatcher.EXACT, + ) // This is an example CameraGraph configuration a camera configured with both capture and // non capture output streams, as well as physical outputs: @@ -82,9 +95,23 @@ internal class FrameDistributor( // to Stream-3 if they are configured with an ImageSource. Each of these streams is // associated with its own OutputDistributor for error handling and grouping. private val imageDistributors = - imageSources.mapValues { (_, imageSource) -> + streamGraphImpl.imageSourceMap.mapValues { (cameraStreamId, imageSource) -> + val cameraStreamConfig = streamGraphImpl.getCameraStreamConfig(cameraStreamId)!! + val imageSourceConfig = cameraStreamConfig.imageSourceConfig!! + val outputMatcher = + selectTimestampMatcher( + cameraStreamId, + cameraStreamConfig, + imageSourceConfig, + isCameraTimebaseRealtime, + realtimeToMonotonicOffsetNs, + ) + val imageDistributor = - OutputDistributor(outputFinalizer = ClosingFinalizer) + OutputDistributor( + outputFinalizer = ClosingFinalizer, + outputMatcher = outputMatcher, + ) // Bind the listener on the ImageSource to the imageDistributor. This listener // and the imageDistributor may be invoked on a different thread. @@ -126,7 +153,7 @@ internal class FrameDistributor( frameInfoDistributor.onOutputStarted( cameraFrameNumber = frameNumber, cameraTimestamp = timestamp, - outputNumber = frameNumber.value, // Number to match output against + cameraOutputNumber = frameNumber.value, // Number to match output against outputListener = frameState.frameInfoOutput, ) @@ -139,7 +166,7 @@ internal class FrameDistributor( imageDistributor.onOutputStarted( cameraFrameNumber = frameNumber, cameraTimestamp = timestamp, - outputNumber = timestamp.value, // Number to match output against + cameraOutputNumber = timestamp.value, // Number to match output against outputListener = imageOutput, ) @@ -241,4 +268,89 @@ internal class FrameDistributor( imageDistributor.close() } } + + @Suppress("NOTHING_TO_INLINE") + companion object { + @JvmStatic + private fun selectTimestampMatcher( + cameraStreamId: StreamId, + cameraStreamConfig: CameraStream.Config, + imageSourceConfig: ImageSourceConfig, + isCameraTimebaseRealtime: Boolean, + realtimeToMonotonicOffsetNs: Long, + ): OutputMatcher { + // This logic mirrors the behavior of Camera3OutputStream.cpp which uses similar logic + // to determine the timebase for outputs. + // + // See frameworks/av/services/camera/libcameraservice/device3/Camera3OutputStream.cpp + // for more details. + + // TODO: Add support for OutputConfiguration.setReadoutTimestampEnabled which changes + // the timestamps of images being produced by the ImageReader. + + // TODO: Consider altering the detection delta for inexact ratios during high-speed + // recording. + if (isCameraTimebaseRealtime) { + if ( + cameraStreamConfig.isDefaultTimebase() && + !imageSourceConfig.isVideoEncodeUsage() && + !imageSourceConfig.isHwComposerUsage() + ) { + return OutputMatcher.EXACT + } else if ( + cameraStreamConfig.isRealtimeTimebase() || cameraStreamConfig.isSensorTimebase() + ) { + return OutputMatcher.EXACT + } + Log.debug { + "Configuring $cameraStreamId with inexact realtime-to-monotonic" + + " timestamp matching rules." + } + return OutputMatcher.forTimestampsWithOffset(realtimeToMonotonicOffsetNs) + } + + // If the Camera Timebase is monotonic... + + if (cameraStreamConfig.isRealtimeTimebase()) { + Log.debug { + "Configuring $cameraStreamId with inexact monotonic-to-realtime" + + " timestamp matching rules." + } + // Camera is in monotonic but outputs are using the realtime timebase. + return OutputMatcher.forTimestampsWithOffset(-realtimeToMonotonicOffsetNs) + } + + // Default case for most non-realtime devices. + return OutputMatcher.EXACT + } + + private inline fun CameraStream.Config.isRealtimeTimebase() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + this.outputs.any { + it.timestampBase == OutputStream.TimestampBase.TIMESTAMP_BASE_REALTIME + } + + private inline fun CameraStream.Config.isSensorTimebase() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + this.outputs.any { + it.timestampBase == OutputStream.TimestampBase.TIMESTAMP_BASE_SENSOR + } + + private inline fun CameraStream.Config.isDefaultTimebase() = + Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || + this.outputs.all { + it.timestampBase == null || + it.timestampBase == OutputStream.TimestampBase.TIMESTAMP_BASE_DEFAULT + } + + private inline fun ImageSourceConfig.isVideoEncodeUsage(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + this.usageFlags != null && + this.usageFlags and HardwareBuffer.USAGE_VIDEO_ENCODE != 0L + + private inline fun ImageSourceConfig.isHwComposerUsage(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + this.usageFlags != null && + this.usageFlags and HardwareBuffer.USAGE_COMPOSER_OVERLAY != 0L + } } diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt index 7a0a6594111a1..ca569fbc0b651 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt @@ -210,7 +210,7 @@ internal class FrameState( override fun onOutputComplete( cameraFrameNumber: FrameNumber, cameraTimestamp: CameraTimestamp, - outputSequence: Long, + cameraOutputSequence: Long, outputNumber: Long, outputResult: OutputResult, ) { @@ -232,7 +232,7 @@ internal class FrameState( override fun onOutputComplete( cameraFrameNumber: FrameNumber, cameraTimestamp: CameraTimestamp, - outputSequence: Long, + cameraOutputSequence: Long, outputNumber: Long, outputResult: OutputResult, ) { diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt index e459713a63d04..f778708554f31 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputDistributor.kt @@ -49,6 +49,7 @@ import kotlinx.atomicfu.atomic internal class OutputDistributor( private val maximumCachedOutputs: Int = 3, private val outputFinalizer: Finalizer, + private val outputMatcher: OutputMatcher, ) : AutoCloseable { internal interface OutputListener { @@ -62,7 +63,7 @@ internal class OutputDistributor( fun onOutputComplete( cameraFrameNumber: FrameNumber, cameraTimestamp: CameraTimestamp, - outputSequence: Long, + cameraOutputSequence: Long, outputNumber: Long, outputResult: OutputResult, ) @@ -72,15 +73,15 @@ internal class OutputDistributor( @GuardedBy("lock") private var closed = false - @GuardedBy("lock") private var outputSequenceNumbers = 1L + @GuardedBy("lock") private var cameraOutputSequenceNumbers = 1L - @GuardedBy("lock") private var newestOutputNumber = Long.MIN_VALUE + @GuardedBy("lock") private var newestCameraOutputNumber = Long.MIN_VALUE @GuardedBy("lock") private var newestFrameNumber = FrameNumber(Long.MIN_VALUE) @GuardedBy("lock") private var lastFailedFrameNumber = Long.MIN_VALUE - @GuardedBy("lock") private var lastFailedOutputNumber = Long.MIN_VALUE + @GuardedBy("lock") private var lastFailedCameraOutputNumber = Long.MIN_VALUE private val startedOutputs = mutableListOf>() private val availableOutputs = mutableMapOf>() @@ -92,7 +93,7 @@ internal class OutputDistributor( * * @param cameraFrameNumber The Camera2 FrameNumber for this output * @param cameraTimestamp The Camera2 CameraTimestamp for this output - * @param outputNumber untyped number that corresponds to the number provided by + * @param cameraOutputNumber untyped number that corresponds to the number provided by * [onOutputResult]. For Images, this will likely be the timestamp of the image (Which may be * the same as the CameraTimestamp, but may also be different if the timebase of the images is * different), or the value of the frameNumber if this OutputDistributor is handling metadata. @@ -103,16 +104,17 @@ internal class OutputDistributor( fun onOutputStarted( cameraFrameNumber: FrameNumber, cameraTimestamp: CameraTimestamp, - outputNumber: Long, + cameraOutputNumber: Long, outputListener: OutputListener, ) { var missingOutputs: List>? = null var matchingOutput: OutputResult? = null var invokeOutputListener = false + var outputNumber: Long? = null var outputToFinalize: OutputResult? = null val isClosed: Boolean - val outputSequence: Long + val cameraOutputSequence: Long synchronized(lock) { // onOutputStarted should only be invoked once per frame per camera CTS requirements. // Check to see that there are no duplicate events. Note that on some platforms, @@ -132,20 +134,29 @@ internal class OutputDistributor( ?.let { Log.warn { "onOutputStarted was invoked multiple times with a previously started output!" + - "onOutputStarted with $cameraFrameNumber, $cameraTimestamp, $outputNumber" + + "onOutputStarted with $cameraFrameNumber, $cameraTimestamp, $cameraOutputNumber" + ". Previously started output: $it. Ignoring." } return } isClosed = closed - outputSequence = outputSequenceNumbers++ + cameraOutputSequence = cameraOutputSequenceNumbers++ if ( closed || lastFailedFrameNumber == cameraFrameNumber.value || - lastFailedOutputNumber == outputNumber + lastFailedCameraOutputNumber == cameraOutputNumber ) { - outputToFinalize = availableOutputs.remove(outputNumber) + val outputToFinalizeKey = + availableOutputs.keys.firstOrNull { + outputMatcher.fuzzyEqual( + cameraOutputNumber = cameraOutputNumber, + outputNumber = it, + ) + } + outputToFinalize = + outputToFinalizeKey?.let { availableOutputs.remove(outputToFinalizeKey) } + outputNumber = outputToFinalizeKey invokeOutputListener = true return@synchronized } @@ -157,19 +168,28 @@ internal class OutputDistributor( } // Determine if the outputNumber is out of order relative to other onOutputStarted calls - val isOutputNumberOutOfOrder = outputNumber < newestOutputNumber + val isOutputNumberOutOfOrder = cameraOutputNumber < newestCameraOutputNumber if (!isOutputNumberOutOfOrder) { - newestOutputNumber = outputNumber + newestCameraOutputNumber = cameraOutputNumber } val isOutOfOrder = isFrameNumberOutOfOrder || isOutputNumberOutOfOrder // Check for matching outputs - if (availableOutputs.containsKey(outputNumber)) { + val matchingOutputKey = + availableOutputs.keys.firstOrNull { + outputMatcher.fuzzyEqual( + cameraOutputNumber = cameraOutputNumber, + outputNumber = it, + ) + } + if (matchingOutputKey != null) { // If we found a matching output, get and remove it from the list of // availableOutputs. - matchingOutput = availableOutputs.remove(outputNumber) + matchingOutput = availableOutputs.remove(matchingOutputKey) + outputNumber = matchingOutputKey invokeOutputListener = true - missingOutputs = removeOutputsOlderThan(isOutOfOrder, outputSequence, outputNumber) + missingOutputs = + removeOutputsOlderThan(isOutOfOrder, cameraOutputSequence, cameraOutputNumber) return@synchronized } @@ -180,17 +200,15 @@ internal class OutputDistributor( isOutOfOrder, cameraFrameNumber, cameraTimestamp, - outputSequence, - outputNumber, + cameraOutputSequence, + cameraOutputNumber, outputListener, ) ) } // Handle missing outputs, finalizers, and listeners outside of the synchronized block. - missingOutputs?.forEach { - it.completeWith(OutputResult.failure(OutputStatus.ERROR_OUTPUT_MISSING)) - } + missingOutputs?.forEach { it.completeWithFailure(OutputStatus.ERROR_OUTPUT_MISSING) } outputToFinalize?.output?.let { outputFinalizer.finalize(it) } if (invokeOutputListener) { @@ -200,11 +218,16 @@ internal class OutputDistributor( } else { matchingOutput ?: OutputResult.failure(OutputStatus.ERROR_OUTPUT_FAILED) } + + // TODO: Consider if outputMatcher should convert the cameraOutputNumber to output + // output number for the -1 case. For exact matches this would work all the time, but + // for inexact matches it could be off by some delta. + val outputNumberForListener = outputNumber ?: -1 outputListener.onOutputComplete( cameraFrameNumber = cameraFrameNumber, cameraTimestamp = cameraTimestamp, - outputSequence = outputSequence, - outputNumber = outputNumber, + cameraOutputSequence = cameraOutputSequence, + outputNumber = outputNumberForListener, outputResult, ) } @@ -222,19 +245,31 @@ internal class OutputDistributor( var outputsToCancel: List>? = null synchronized(lock) { - if (closed || lastFailedOutputNumber == outputNumber) { + if ( + closed || + outputMatcher.fuzzyEqual( + cameraOutputNumber = lastFailedCameraOutputNumber, + outputNumber = outputNumber, + ) + ) { outputToFinalize = outputResult return@synchronized } - val matchingOutput = startedOutputs.firstOrNull { it.outputNumber == outputNumber } + val matchingOutput = + startedOutputs.firstOrNull { + outputMatcher.fuzzyEqual( + cameraOutputNumber = it.cameraOutputNumber, + outputNumber = outputNumber, + ) + } // Complete the matching output, if possible, and remove it from the list of started // outputs. if (matchingOutput != null) { outputsToCancel = removeOutputsOlderThan(matchingOutput) - matchingOutput.completeWith(outputResult) + matchingOutput.completeWith(outputNumber, outputResult) startedOutputs.remove(matchingOutput) return@synchronized @@ -253,9 +288,7 @@ internal class OutputDistributor( // Invoke finalizers and listeners outside of the synchronized block to avoid holding locks. outputToFinalize?.output?.let { outputFinalizer.finalize(it) } - outputsToCancel?.forEach { - it.completeWith(OutputResult.failure(OutputStatus.ERROR_OUTPUT_MISSING)) - } + outputsToCancel?.forEach { it.completeWithFailure(OutputStatus.ERROR_OUTPUT_MISSING) } } /** Indicates an output will not arrive for a specific [FrameNumber]. */ @@ -270,7 +303,7 @@ internal class OutputDistributor( startedOutputs .singleOrNull { it.cameraFrameNumber == frameNumber } ?.let { - lastFailedOutputNumber = it.outputNumber + lastFailedCameraOutputNumber = it.cameraOutputNumber startedOutputs.remove(it) outputWithFailure = it } @@ -282,12 +315,16 @@ internal class OutputDistributor( @GuardedBy("lock") private fun removeOutputsOlderThan(output: StartedOutput): List> = - removeOutputsOlderThan(output.isOutOfOrder, output.outputSequence, output.outputNumber) + removeOutputsOlderThan( + output.isOutOfOrder, + output.cameraOutputSequence, + output.cameraOutputNumber, + ) private fun removeOutputsOlderThan( isOutOfOrder: Boolean, - outputSequence: Long, - outputNumber: Long, + cameraOutputSequence: Long, + cameraOutputNumber: Long, ): List> { // This filter is bi-modal: If [output] is outOfOrder, it will only remove *other* out of // order events that are older than the most recent event. Similarly, if it's normal and in @@ -295,8 +332,8 @@ internal class OutputDistributor( val outputsToCancel = startedOutputs.filter { it.isOutOfOrder == isOutOfOrder && - it.outputSequence < outputSequence && - it.outputNumber < outputNumber + it.cameraOutputSequence < cameraOutputSequence && + it.cameraOutputNumber < cameraOutputNumber } startedOutputs.removeAll(outputsToCancel) return outputsToCancel @@ -334,24 +371,24 @@ internal class OutputDistributor( val isOutOfOrder: Boolean, val cameraFrameNumber: FrameNumber, val cameraTimestamp: CameraTimestamp, - val outputSequence: Long, - val outputNumber: Long, + val cameraOutputSequence: Long, + val cameraOutputNumber: Long, private val outputListener: OutputListener, ) { private val complete = atomic(false) fun completeWithFailure(failureReason: OutputStatus) = - completeWith(OutputResult.failure(failureReason)) + completeWith(-1L, OutputResult.failure(failureReason)) - fun completeWith(outputResult: OutputResult) { + fun completeWith(outputNumber: Long, outputResult: OutputResult) { check(complete.compareAndSet(expect = false, update = true)) { - "Output $outputSequence at $cameraFrameNumber for $outputNumber was completed " + - "multiple times!" + "Output $cameraOutputSequence at $cameraFrameNumber for $outputNumber was " + + "completed multiple times!" } outputListener.onOutputComplete( cameraFrameNumber, cameraTimestamp, - outputSequence, + cameraOutputSequence, outputNumber, outputResult, ) diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputMatcher.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputMatcher.kt new file mode 100644 index 0000000000000..b4bc81f2cfd69 --- /dev/null +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/OutputMatcher.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2026 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.internal + +import androidx.annotation.VisibleForTesting +import kotlinx.atomicfu.atomic + +/** + * Stateful comparison tool for matching output numbers. + * + * Exact matching will only honor timestamps that match the nanosecond timestamp exactly. Fuzzy + * matching will match timestamps within an error margin. Fuzzy matching with offset will match + * timestamps within an error margin and offset applied. + * + * Offset should be given such that `sensorTimestampNs + offsetNs = imageTimestampNs`. + * + * @param initialOffset given such that sensorTimestampNs + offsetNs = imageTimestampNs + * @param errorDelta given such that sensorTimestampNs + offsetNs = imageTimestampNs +- errorNs + */ +internal class OutputMatcher +@VisibleForTesting +internal constructor(initialOffset: Long = 0L, private val errorDelta: Long) { + private val currentOffset = atomic(initialOffset) + + /** + * Check to see if a sensor timestamp is equal to an image timestamp. If they match, the offset + * is updated. + */ + fun fuzzyEqual(cameraOutputNumber: Long, outputNumber: Long): Boolean { + val offsetNs = currentOffset.value + val delta = cameraOutputNumber - outputNumber + offsetNs + if (delta == 0L) { + // Timestamps match exactly + return true + } else if (errorDelta != 0L && delta < errorDelta && delta > -errorDelta) { + // Inexact match within error margins: update currentOffsetNs + currentOffset.compareAndSet(offsetNs, offsetNs - delta) + return true + } + // Did not match + return false + } + + /** Check to see if an image timestamp is less than a sensor timestamp. */ + fun fuzzyLessThan(cameraOutputNumber: Long, outputNumber: Long): Boolean { + // Example: + // img ssr off error v <= + // 0 - 100 - 105 + 6 = -199 true + // 198 - 100 - 105 + 6 = -1 true // less than + // 205 - 100 - 105 + 6 = 6 false // exact equals + // 207 - 100 - 105 + 6 = 8 false // fuzzy equals + // 215 - 100 - 105 + 6 = 16 false // greater than + return outputNumber - cameraOutputNumber - currentOffset.value + errorDelta < 0 + } + + /** Check to see if an image timestamp is less than a sensor timestamp. */ + fun fuzzyLessThanOrEqual(cameraOutputNumber: Long, outputNumber: Long): Boolean { + // Example: + // img ssr off error v <= + // 0 - 100 - 105 - 6 = -211 true + // 198 - 100 - 105 - 6 = -13 true // less than + // 205 - 100 - 105 - 6 = -6 true // exact equals + // 207 - 100 - 105 - 6 = -4 true // fuzzy equals + // 215 - 100 - 105 - 6 = 4 false // greater than + return outputNumber - cameraOutputNumber - currentOffset.value - errorDelta <= 0 + } + + fun fuzzyGreaterThanOrEqual(sensorTimestampNs: Long, imageTimestampNs: Long): Boolean { + return !fuzzyLessThan(sensorTimestampNs, imageTimestampNs) + } + + fun fuzzyGreaterThan(sensorTimestampNs: Long, imageTimestampNs: Long): Boolean { + return !fuzzyLessThanOrEqual(sensorTimestampNs, imageTimestampNs) + } + + companion object { + val EXACT = OutputMatcher(0, 0) + + /** + * Create an [OutputMatcher] designed to match based on nanosecond-precision timestamps that + * may skew over time and expects the frame rate to be less than 60fps. + */ + fun forTimestampsWithOffset( + initialOffset: Long, + errorDelta: Long = DEFAULT_ERROR_NS, + ): OutputMatcher = OutputMatcher(initialOffset, errorDelta) + + private const val NS_PER_SECOND: Long = 1000000000 + private const val ERROR_DETECTION_FPS: Long = 60 + + // This default assumes that a camera will run at (at most) 60fps + private const val DEFAULT_ERROR_NS = NS_PER_SECOND / (ERROR_DETECTION_FPS * 2) + } +} diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt index 9b717d5f15b40..07e0d034fc01f 100644 --- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt @@ -121,7 +121,7 @@ internal class CameraGraphImplTest { private val cameraControllerProvider: () -> CameraControllerSimulator = { cameraController } private val streamGraph = StreamGraphImpl(metadata, graphConfig, imageSources, cameraControllerProvider) - private val frameDistributor = FrameDistributor(streamGraph.imageSourceMap, frameCaptureQueue) + private val frameDistributor = FrameDistributor(streamGraph, frameCaptureQueue, true, 0L) private val surfaceGraph = SurfaceGraph(streamGraph, cameraControllerProvider, cameraSurfaceManager, emptyMap()) private val audioRestriction = FakeAudioRestrictionController() diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameDistributorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameDistributorTest.kt index 2bbeb76ed6e86..b4781ba7c0ce8 100644 --- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameDistributorTest.kt +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameDistributorTest.kt @@ -26,6 +26,7 @@ import androidx.camera.camera2.pipe.FrameCapture import androidx.camera.camera2.pipe.FrameNumber import androidx.camera.camera2.pipe.FrameReference import androidx.camera.camera2.pipe.FrameReference.Companion.acquire +import androidx.camera.camera2.pipe.ImageSourceConfig import androidx.camera.camera2.pipe.OutputStatus import androidx.camera.camera2.pipe.Request import androidx.camera.camera2.pipe.StreamFormat @@ -43,13 +44,21 @@ import org.robolectric.annotation.Config /** Tests for [FrameDistributor] */ @RunWith(RobolectricTestRunner::class) -@Config(sdk = [Config.ALL_SDKS]) +@Config(sdk = [Config.NEWEST_SDK]) class FrameDistributorTest { private val stream1Config = - CameraStream.Config.create(Size(1280, 720), StreamFormat.YUV_420_888) + CameraStream.Config.create( + Size(1280, 720), + StreamFormat.YUV_420_888, + imageSourceConfig = ImageSourceConfig(5), + ) private val stream2Config = - CameraStream.Config.create(Size(1920, 1080), StreamFormat.YUV_420_888) + CameraStream.Config.create( + Size(1920, 1080), + StreamFormat.YUV_420_888, + imageSourceConfig = ImageSourceConfig(5), + ) private val streamConfigs = listOf(stream1Config, stream2Config) private val imageSimulator = ImageSimulator(streamConfigs) @@ -73,13 +82,13 @@ class FrameDistributorTest { private val fakeFrameBuffer = FakeFrameBuffer() private val frameCaptureQueue = FrameCaptureQueue() private val frameDistributor = - FrameDistributor(imageSimulator.imageSources, frameCaptureQueue).also { + FrameDistributor(imageSimulator.streamGraph, frameCaptureQueue, true, 0L).also { it.frameStartedListener = fakeFrameBuffer } @Test fun frameDistributorSetupVerification() { - assertThat(imageSimulator.imageSources.keys).containsExactly(stream1Id, stream2Id) + assertThat(imageSimulator.streamGraph.streamIds).containsExactly(stream1Id, stream2Id) assertThat(imageSimulator.streamToSurfaceMap.keys).containsExactly(stream1Id, stream2Id) } diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt index f9f004b932ed6..2355ceb1a96c9 100644 --- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputDistributorTest.kt @@ -32,7 +32,7 @@ import org.robolectric.annotation.Config /** Tests for [OutputDistributor] */ @RunWith(RobolectricTestRunner::class) -@Config(sdk = [Config.ALL_SDKS]) +@Config(sdk = [Config.NEWEST_SDK]) class OutputDistributorTest { private val fakeOutput1 = FakeOutput(101) private val fakeOutput2 = FakeOutput(102) @@ -59,6 +59,7 @@ class OutputDistributorTest { value?.finalize() } }, + outputMatcher = OutputMatcher.EXACT, ) @Test @@ -577,7 +578,7 @@ class OutputDistributorTest { override fun onOutputComplete( cameraFrameNumber: FrameNumber, cameraTimestamp: CameraTimestamp, - outputSequence: Long, + cameraOutputSequence: Long, outputNumber: Long, outputResult: OutputResult, ) { @@ -590,7 +591,7 @@ class OutputDistributorTest { assertThat(outputNumber).isEqualTo(outputNumber) // Record the actual output and outputSequence for future checks. - this.outputSequence = outputSequence + this.outputSequence = cameraOutputSequence this.outputStatus = outputResult.status this.output = outputResult.output } diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputMatcherTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputMatcherTest.kt new file mode 100644 index 0000000000000..2e1cd0e72e15a --- /dev/null +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/OutputMatcherTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright 2026 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.internal + +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Tests for [OutputMatcher]. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Config.NEWEST_SDK]) +class OutputMatcherTest { + + @Test + fun matcher_fuzzyEqual() { + val matcher = OutputMatcher(initialOffset = 105, errorDelta = 6) + + assertThat(matcher.fuzzyEqual(100, 0)).isFalse() + assertThat(matcher.fuzzyEqual(100, 198)).isFalse() + assertThat(matcher.fuzzyEqual(100, 205)).isTrue() + assertThat(matcher.fuzzyEqual(100, 207)).isTrue() + assertThat(matcher.fuzzyEqual(100, 215)).isFalse() + } + + @Test + fun matcher_fuzzyLessThan() { + // Example: + // img ssr off error v < + // 0 - 100 - 105 + 6 = -199 true + // 198 - 100 - 105 + 6 = -1 true // less than + // 205 - 100 - 105 + 6 = 6 false // exact equals + // 207 - 100 - 105 + 6 = 8 false // fuzzy equals + // 215 - 100 - 105 + 6 = 16 false // greater than + + val matcher = OutputMatcher(initialOffset = 105, errorDelta = 6) + + assertThat(matcher.fuzzyLessThan(100, 0)).isTrue() + assertThat(matcher.fuzzyLessThan(100, 198)).isTrue() + assertThat(matcher.fuzzyLessThan(100, 205)).isFalse() + assertThat(matcher.fuzzyLessThan(100, 207)).isFalse() + assertThat(matcher.fuzzyLessThan(100, 215)).isFalse() + } + + @Test + fun matcher_fuzzyLessThanEquals() { + // Example: + // img ssr off error v <= + // 0 - 100 - 105 - 6 = -211 true + // 198 - 100 - 105 - 6 = -13 true // less than + // 205 - 100 - 105 - 6 = -6 true // exact equals + // 207 - 100 - 105 - 6 = -4 true // fuzzy equals + // 215 - 100 - 105 - 6 = 4 false // greater than + + val matcher = OutputMatcher(initialOffset = 105, errorDelta = 6) + + assertThat(matcher.fuzzyLessThanOrEqual(100, 0)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(100, 198)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(100, 205)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(100, 207)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(100, 215)).isFalse() + } + + @Test + fun matcher_realExampleTest() { + val matcher = OutputMatcher.forTimestampsWithOffset(initialOffset = -36480495215790L) + + assertThat(matcher.fuzzyEqual(86161121748100L, 49680626532310L)).isTrue() + assertThat(matcher.fuzzyEqual(86161121748100L, 49680626500000L)).isTrue() + assertThat(matcher.fuzzyEqual(86161121748100L, 49680610000000L)).isFalse() + assertThat(matcher.fuzzyLessThan(86161121748100L, 49680660307860L)).isFalse() + } + + @Test + fun matcher_exactBehavesTheSameAsNormalCompare() { + val matcher = OutputMatcher.EXACT + + assertThat(matcher.fuzzyEqual(42, 41)).isFalse() + assertThat(matcher.fuzzyEqual(42, 42)).isTrue() + assertThat(matcher.fuzzyEqual(42, 43)).isFalse() + + assertThat(matcher.fuzzyLessThan(42, 41)).isTrue() + assertThat(matcher.fuzzyLessThan(42, 42)).isFalse() + assertThat(matcher.fuzzyLessThan(42, 43)).isFalse() + + assertThat(matcher.fuzzyLessThanOrEqual(42, 41)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(42, 42)).isTrue() + assertThat(matcher.fuzzyLessThanOrEqual(42, 43)).isFalse() + + assertThat(matcher.fuzzyGreaterThan(42, 41)).isFalse() + assertThat(matcher.fuzzyGreaterThan(42, 42)).isFalse() + assertThat(matcher.fuzzyGreaterThan(42, 43)).isTrue() + + assertThat(matcher.fuzzyGreaterThanOrEqual(42, 41)).isFalse() + assertThat(matcher.fuzzyGreaterThanOrEqual(42, 42)).isTrue() + assertThat(matcher.fuzzyGreaterThanOrEqual(42, 43)).isTrue() + } + + @Test + fun fuzzyEqual_updatesOffsetOnInexactMatch() { + // Start with offset 100, error delta 10. + // Formula: delta = cameraOutputNumber - outputNumber + currentOffset + val matcher = OutputMatcher(initialOffset = 100L, errorDelta = 10L) + + // 1. Exact match: delta is 0. Offset should NOT change. + // 500 - 600 + 100 = 0 + assertThat(matcher.fuzzyEqual(500, 600)).isTrue() + + // 2. Inexact match within error delta (10). + // 500 - 605 + 100 = -5 (which is > -10 and < 10) + // New offset should become: offset - delta => 100 - (-5) = 105 + assertThat(matcher.fuzzyEqual(500, 605)).isTrue() + + // 3. Verify offset updated to 105 by checking an exact match with the new offset. + // 500 - 614 + 105 = 0 (Fuzzy match succeeds because internal delta was updated) + assertThat(matcher.fuzzyEqual(500, 614)).isTrue() + } + + @Test + fun fuzzyEqual_doesNotUpdateOffsetOnFailedMatch() { + val matcher = OutputMatcher(initialOffset = 100L, errorDelta = 10L) + + // Match fails (delta = 20, which is > errorDelta 10) + // 500 - 580 + 100 = 20 + assertThat(matcher.fuzzyEqual(500, 580)).isFalse() + + // Verify offset is still 100 by performing an exact match + assertThat(matcher.fuzzyEqual(500, 600)).isTrue() + } + + @Test + fun fuzzyEqual_handlesLargeDriftUpdates() { + // Simulate a scenario where the clocks are slowly drifting + val matcher = OutputMatcher(initialOffset = 0L, errorDelta = 100L) + + // First match at 1000, but image is at 1050. + // Delta = 1000 - 1050 + 0 = -50. New Offset = 50. + assertThat(matcher.fuzzyEqual(1000, 1050)).isTrue() + + // Second match: sensor at 2000. + // If offset was still 0, 2000 - 2050 + 0 = -50 (fuzzy) + // Since offset is now 50, 2000 - 2050 + 50 = 0 (exact) + assertThat(matcher.fuzzyEqual(2000, 2050)).isTrue() + } +} diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt index 875204fdd9a6a..107a8f3b84c76 100644 --- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt @@ -20,50 +20,28 @@ import androidx.camera.camera2.pipe.CameraGraph import androidx.camera.camera2.pipe.CameraMetadata import androidx.camera.camera2.pipe.CameraStream import androidx.camera.camera2.pipe.OutputId -import androidx.camera.camera2.pipe.StreamGraph import androidx.camera.camera2.pipe.StreamId import androidx.camera.camera2.pipe.graph.StreamGraphImpl -import androidx.camera.camera2.pipe.media.ImageSource import org.mockito.kotlin.mock -class ImageSimulator( +internal class ImageSimulator( streamConfigs: List, - imageStreams: Set? = null, defaultCameraMetadata: CameraMetadata? = null, - defaultStreamGraph: StreamGraph? = null, ) : AutoCloseable { private val fakeSurfaces = FakeSurfaces() private val fakeImageReaders = FakeImageReaders(fakeSurfaces) + private val fakeImageSources = FakeImageSources(fakeImageReaders) val cameraMetadata = defaultCameraMetadata ?: FakeCameraMetadata() val graphConfig = CameraGraph.Config(camera = cameraMetadata.camera, streams = streamConfigs) - val streamGraph = - defaultStreamGraph ?: StreamGraphImpl(cameraMetadata, graphConfig, mock(), mock()) - private val fakeImageSources = buildMap { - for (config in graphConfig.streams) { - if (imageStreams != null && !imageStreams.contains(config)) continue - val cameraStream = streamGraph[config]!! - val fakeImageSource = - FakeImageSource.create( - config.outputs.first().format, - cameraStream.id, - cameraStream.outputs.associate { it.id to it.size }, - 5, - fakeImageReaders, - ) - check(this[cameraStream.id] == null) - this[cameraStream.id] = fakeImageSource - } - } + val streamGraph = StreamGraphImpl(cameraMetadata, graphConfig, fakeImageSources, mock()) - val imageSources: Map = fakeImageSources - val imageStreams = imageSources.keys val streamToSurfaceMap = buildMap { for (config in graphConfig.streams) { val cameraStream = streamGraph[config]!! this[cameraStream.id] = - imageSources[cameraStream.id]?.surface + fakeImageSources[cameraStream.id]?.surface ?: fakeSurfaces.createFakeSurface(cameraStream.outputs.first().size) } } @@ -73,9 +51,6 @@ class ImageSimulator( } override fun close() { - for (imageSource in fakeImageSources.values) { - imageSource.close() - } fakeSurfaces.close() } } diff --git a/compose/material3/material3/src/androidHostTest/kotlin/androidx/compose/material3/carousel/CenteredHeroTest.kt b/compose/material3/material3/src/androidHostTest/kotlin/androidx/compose/material3/carousel/CenteredHeroTest.kt index f63d29e05b14f..1f2d1ae3db919 100644 --- a/compose/material3/material3/src/androidHostTest/kotlin/androidx/compose/material3/carousel/CenteredHeroTest.kt +++ b/compose/material3/material3/src/androidHostTest/kotlin/androidx/compose/material3/carousel/CenteredHeroTest.kt @@ -121,4 +121,90 @@ class CenteredHeroTest { assertThat(strategy.itemMainAxisSize).isEqualTo(120f) } + + @Test + fun maxItemWidth_withoutItemSpacing_firstAndLastDefaultItemsShouldAlignToStartAndEnd() { + val keylineList = + heroKeylineList( + density = Density, + carouselMainAxisSize = 300f + 300f + 300f + 40f + 40f, + maxItemSize = 300f, + itemSpacing = 0f, + itemCount = 7, + isCentered = true, + ) + + val strategy = + Strategy( + defaultKeylines = keylineList, + availableSpace = 300f + 300f + 300f + 40f + 40f, + itemSpacing = 0f, + beforeContentPadding = 0f, + afterContentPadding = 0f, + ) + + assertThat(strategy.defaultKeylines.firstNonAnchor.offset).isEqualTo(20f) + assertThat(strategy.defaultKeylines.lastNonAnchor.offset) + .isEqualTo((300f + 300f + 300f + 40f + 40f) - 20f) + } + + @Test + fun maxItemWidth_withItemSpacing_firstAndLastDefaultItemsShouldAlignToStartAndEnd() { + // Create a keyline list with 3 large and 2 small items. The resulting + // small items should have a size of 40f + val keylineList = + heroKeylineList( + density = Density, + carouselMainAxisSize = 300f + 300f + 300f + 40f + 40f, + maxItemSize = 300f, + itemSpacing = 12f, + itemCount = 7, + isCentered = true, + ) + + val strategy = + Strategy( + defaultKeylines = keylineList, + availableSpace = 300f + 300f + 300f + 40f + 40f, + itemSpacing = 12f, + beforeContentPadding = 0f, + afterContentPadding = 0f, + ) + + // Small items in the default keyline list will be at the start and end of the list. + // They should be flush against the container (same as when there is no item spacing). + assertThat(strategy.defaultKeylines.firstNonAnchor.offset).isEqualTo(20f) + assertThat(strategy.defaultKeylines.lastNonAnchor.offset) + .isEqualTo((300f + 300f + 300f + 40f + 40f) - 20f) + } + + @Test + fun maxItemWidth_withItemSpacing_evenCount_firstAndLastDefaultItemsShouldAlignToStartAndEnd() { + // Create a keyline list with 3 large and 2 small items. The resulting + // small items should have a size of 40f + val keylineList = + heroKeylineList( + density = Density, + carouselMainAxisSize = 300f + 300f + 300f + 40f + 40f, + maxItemSize = 300f, + itemSpacing = 12f, + itemCount = 10, + isCentered = true, + ) + + val strategy = + Strategy( + defaultKeylines = keylineList, + availableSpace = 300f + 300f + 300f + 40f + 40f, + itemSpacing = 12f, + beforeContentPadding = 0f, + afterContentPadding = 0f, + ) + + // Small items in the default keyline list will be at the start and end of the list. + // They should be flush against the container (same as when there is no item spacing). + assertThat(strategy.defaultKeylines.firstNonAnchor.offset).isEqualTo(20f) + assertThat(strategy.defaultKeylines.lastNonAnchor.offset) + .isEqualTo((300f + 300f + 300f + 40f + 40f) - 20f) + } } diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt index 521977ca71752..5bd36d4780cf2 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt @@ -358,9 +358,15 @@ private class KeylineListScopeImpl : KeylineListScope { } else { itemSpacing / 2f } + // Count the number of item spaces between the center of the viewport and the + // first focal item. We then need to use this count to get a total item spacing + // number to subtract for an accurate pivot offset. + val itemSpaceCounts = (focalItemCount / 2) * itemSpacing + (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount) - - itemSpacingSplit + itemSpacingSplit - + itemSpaceCounts } CarouselAlignment.End -> carouselMainAxisSize - (focalItemSize / 2) // Else covers and defaults to CarouselAlignment.Start diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt index fb1afc1f6cd27..91e3c5eaa969d 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt @@ -277,7 +277,7 @@ internal fun heroKeylineList( return if (shouldCenter && itemCount >= arrangement.itemCount()) { // When centered and when there are enough items to fill all keyline positions, create // a centered arrangement. - return createCenterAlignedKeylineList( + createCenterAlignedKeylineList( carouselMainAxisSize = carouselMainAxisSize, itemSpacing = itemSpacing, rightAnchorSize = anchorSize, diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/action/CombinedAction.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/action/CombinedAction.kt new file mode 100644 index 0000000000000..dc1f0aa58d888 --- /dev/null +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/action/CombinedAction.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2026 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:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + +package androidx.compose.remote.creation.compose.action + +import androidx.annotation.RestrictTo + +/** Creates an action that's a composite of multiple actions. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public class CombinedAction(public vararg val actions: Action) : Action { + override fun toRemoteAction(): androidx.compose.remote.creation.actions.Action { + return androidx.compose.remote.creation.actions.Action { writer -> + for (action in actions) { + action.toRemoteAction().write(writer) + } + } + } +} diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/capture/CaptureRemoteDocument.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/capture/CaptureRemoteDocument.kt index 1dbfea6b51f3c..0482c2f5e833b 100644 --- a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/capture/CaptureRemoteDocument.kt +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/capture/CaptureRemoteDocument.kt @@ -19,17 +19,15 @@ package androidx.compose.remote.creation.compose.capture import android.content.Context -import androidx.collection.emptyIntObjectMap import androidx.compose.remote.creation.CreationDisplayInfo import androidx.compose.remote.creation.compose.ExperimentalRemoteCreationComposeApi import androidx.compose.remote.creation.compose.RemoteComposeCreationComposeFlags import androidx.compose.remote.creation.compose.layout.RemoteComposable -import androidx.compose.remote.creation.compose.v2.captureRemoteDocumentV2 +import androidx.compose.remote.creation.compose.v2.captureSingleRemoteDocumentV2 import androidx.compose.remote.creation.profile.Profile import androidx.compose.remote.creation.profile.RcPlatformProfiles import androidx.compose.runtime.Composable import kotlin.coroutines.resume -import kotlinx.coroutines.flow.first import kotlinx.coroutines.suspendCancellableCoroutine /** @@ -54,15 +52,11 @@ public suspend fun captureSingleRemoteDocument( content: @Composable @RemoteComposable () -> Unit, ): CapturedDocument { if (RemoteComposeCreationComposeFlags.isRemoteApplierEnabled) { - val bytes = - captureRemoteDocumentV2( - creationDisplayInfo = creationDisplayInfo, - profile = profile, - content = content, - ) - .first() - // TODO: WriterEvents/PendingIntents support in V2 - return CapturedDocument(bytes, emptyIntObjectMap()) + return captureSingleRemoteDocumentV2( + creationDisplayInfo = creationDisplayInfo, + profile = profile, + content = content, + ) } return suspendCancellableCoroutine { continuation -> diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/layout/RemoteText.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/layout/RemoteText.kt index 049e19799d4f1..20adb45bb5b85 100644 --- a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/layout/RemoteText.kt +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/layout/RemoteText.kt @@ -123,24 +123,6 @@ public fun RemoteText( style: TextStyle = LocalTextStyle.current, fontVariationSettings: FontVariation.Settings? = null, ) { - if (currentComposer.applier is RemoteComposeApplierV2) { - RemoteTextV2( - remoteText = text, - modifier = modifier, - color = color, - fontSize = fontSize, - fontWeight = fontWeight, - fontStyle = fontStyle, - fontFamily = fontFamily, - textAlign = textAlign, - overflow = overflow, - maxLines = maxLines, - textDecoration = style.textDecoration ?: TextDecoration.None, - fontVariationSettings = fontVariationSettings, - ) - return - } - val textColor = color ?: RemoteColor(style.color.takeOrElse { Color.Black }) val style = @@ -210,6 +192,29 @@ public fun RemoteText( ) { val captureMode = LocalRemoteComposeCreationState.current + if (currentComposer.applier is RemoteComposeApplierV2) { + RemoteTextV2( + text = text, + modifier = modifier, + color = color, + fontSize = fontSize, + fontWeight = fontWeight, + fontStyle = fontStyle, + fontFamily = fontFamily, + textAlign = textAlign, + overflow = overflow, + maxLines = maxLines, + minFontSize = minFontSize, + maxFontSize = maxFontSize, + letterSpacing = letterSpacing, + lineHeightAdd = lineHeightAdd, + lineHeightMultiply = lineHeightMultiply, + textDecoration = textDecoration ?: TextDecoration.None, + fontVariationSettings = fontVariationSettings, + ) + return + } + val useCoreTextComponent = LocalRemoteComposeCreationState.current.profile.supportedOperations.contains( Operations.CORE_TEXT diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/CaptureRemoteDocumentV2.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/CaptureRemoteDocumentV2.kt index f8264c241bf28..1d2b322b25a46 100644 --- a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/CaptureRemoteDocumentV2.kt +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/CaptureRemoteDocumentV2.kt @@ -14,26 +14,63 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package androidx.compose.remote.creation.compose.v2 import androidx.annotation.RestrictTo +import androidx.compose.remote.core.RemoteClock +import androidx.compose.remote.core.SystemClock import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.compose.capture.CapturedDocument import androidx.compose.remote.creation.compose.capture.LocalRemoteComposeCreationState import androidx.compose.remote.creation.compose.capture.RemoteComposeCreationState import androidx.compose.remote.creation.compose.capture.WriterEvents import androidx.compose.remote.creation.profile.Profile import androidx.compose.remote.creation.profile.RcPlatformProfiles +import androidx.compose.runtime.BroadcastFrameClock import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Recomposer import androidx.compose.runtime.snapshots.Snapshot +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.yield + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public suspend fun captureSingleRemoteDocumentV2( + creationDisplayInfo: CreationDisplayInfo, + clock: RemoteClock = SystemClock(), + profile: Profile = RcPlatformProfiles.ANDROIDX, + context: CoroutineContext = Dispatchers.Default, + content: @Composable () -> Unit, +): CapturedDocument { + val writerEvents = WriterEvents() + val document = + captureRemoteDocumentV2( + creationDisplayInfo = creationDisplayInfo, + clock = clock, + writerEvents = writerEvents, + profile = profile, + context = context, + content = content, + ) + .first() + return CapturedDocument(document, writerEvents.pendingIntents) +} /** * Captures a RemoteCompose document using the V2 implementation. Emits a new [ByteArray] every time @@ -42,48 +79,57 @@ import kotlinx.coroutines.launch @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun captureRemoteDocumentV2( creationDisplayInfo: CreationDisplayInfo, + clock: RemoteClock = SystemClock(), + writerEvents: WriterEvents, profile: Profile = RcPlatformProfiles.ANDROIDX, - context: CoroutineContext = Dispatchers.Main, + context: CoroutineContext = Dispatchers.Default, content: @Composable () -> Unit, -): Flow = callbackFlow { +): Flow = flow { val rootNode = RemoteRootNodeV2() val applier = RemoteComposeApplierV2(rootNode) + val writerEvents = WriterEvents() - val launchContext = context.minusKey(kotlinx.coroutines.Job) - val recomposer = Recomposer(launchContext) + val recomposer = Recomposer(currentCoroutineContext()) val composition = Composition(applier, recomposer) - val writerEvents = WriterEvents() + try { + val creationState = RemoteComposeCreationState(creationDisplayInfo, profile, writerEvents) - val creationState = RemoteComposeCreationState(creationDisplayInfo, profile, writerEvents) + composition.setContent { + CompositionLocalProvider( + LocalRemoteComposeCreationState provides creationState, + LocalDensity provides Density(creationDisplayInfo.density), + content = content, + ) + } - composition.setContent { - CompositionLocalProvider( - LocalRemoteComposeCreationState provides creationState, - content = content, - ) - } + val frameClock = BroadcastFrameClock() + coroutineScope { + launch(frameClock) { recomposer.runRecomposeAndApplyChanges() } - launch(launchContext) { recomposer.runRecomposeAndApplyChanges() } + // Make sure runRecomposeAndApplyChanges will pick this up + yield() + frameClock.sendFrame(clock.nanoTime()) - // Launch a collector for recomposer state to trigger renders - launch(launchContext) { - recomposer.currentState.collect { state -> - if (state == Recomposer.State.Idle) { - Snapshot.withMutableSnapshot { - // Create a fresh writer for each emission to ensure a complete document is - // captured - val writer = profile.create(creationDisplayInfo, writerEvents) - creationState.document = writer + // Launch a collector for recomposer state to trigger renders + val documentFlow = + recomposer.currentState + .filter { it == Recomposer.State.Idle } + .mapLatest { + Snapshot.withMutableSnapshot { + // Create a fresh writer for each emission to ensure a complete document + // is + // captured + val writer = profile.create(creationDisplayInfo, writerEvents) + creationState.document = writer - rootNode.render(creationState) - trySend(writer.encodeToByteArray()) - } - } + rootNode.render(creationState) + writer.encodeToByteArray() + } + } + emitAll(documentFlow) } - } - - awaitClose { + } finally { composition.dispose() recomposer.cancel() } diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteComposeNodeV2.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteComposeNodeV2.kt index 3fbc6138ab9a7..50b00e4e656d5 100644 --- a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteComposeNodeV2.kt +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteComposeNodeV2.kt @@ -17,6 +17,8 @@ package androidx.compose.remote.creation.compose.v2 import androidx.annotation.RestrictTo +import androidx.compose.remote.core.Operations +import androidx.compose.remote.core.operations.layout.managers.TextLayout import androidx.compose.remote.creation.compose.capture.RecordingCanvas import androidx.compose.remote.creation.compose.capture.RemoteComposeCreationState import androidx.compose.remote.creation.compose.layout.RemoteAlignment @@ -38,12 +40,10 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontVariation -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.util.fastForEach import androidx.core.graphics.createBitmap @@ -140,70 +140,123 @@ internal class RemoteColumnNodeV2 : RemoteComposeNodeV2() { } internal class RemoteTextNodeV2 : RemoteComposeNodeV2() { - var text: String? = null - var remoteText: RemoteString? = null + lateinit var text: RemoteString var color: RemoteColor? = null - var fontSize: TextUnit = TextUnit.Unspecified - var fontWeight: FontWeight? = null + var fontSize: RemoteFloat = 14f.rf + var fontWeight: RemoteFloat = 400f.rf var fontStyle: FontStyle? = null - var fontFamily: FontFamily? = null + var fontFamily: String? = null var textAlign: TextAlign? = null var overflow: TextOverflow = TextOverflow.Clip var maxLines: Int = Int.MAX_VALUE + var minFontSize: Float? = null + var maxFontSize: Float? = null + var letterSpacing: Float? = null + var lineHeightAdd: Float? = null + var lineHeightMultiply: Float? = null var textDecoration: TextDecoration = TextDecoration.None var fontVariationSettings: FontVariation.Settings? = null + private fun extractFontSettings( + settings: List? + ): Pair?, FloatArray?> { + val size = settings?.size ?: return Pair(null, null) + + val fontAxisNames = Array(size) { settings[it].axisName } + val fontAxisValues = FloatArray(size) { settings[it].toVariationValue(null) } + + return Pair(fontAxisNames, fontAxisValues) + } + override fun render(creationState: RemoteComposeCreationState) { - val textId = - remoteText?.getIdForCreationState(creationState) - ?: text?.let { creationState.document.addText(it) } - ?: 0 + val useCoreTextComponent = + creationState.profile.supportedOperations.contains(Operations.CORE_TEXT) - val colorInt = color?.constantValue?.toArgb() ?: android.graphics.Color.BLACK - val colorId = - if (color?.hasConstantValue == false) { - color!!.getIdForCreationState(creationState) - } else { - -1 - } + if (useCoreTextComponent) { + val textIdValue = text.getIdForCreationState(creationState) - // density is available in creationState if needed, but for now we continue with simplified - // logic - val fontSizePx = - if (fontSize == TextUnit.Unspecified) { - 14f * creationState.creationDisplayInfo.density - } else { - fontSize.value * creationState.creationDisplayInfo.density - } + val colorInt = color?.constantValue?.toArgb() ?: android.graphics.Color.BLACK + val colorId = + if (color?.hasConstantValue == false) { + color!!.getIdForCreationState(creationState) + } else { + -1 + } - creationState.document.textComponent( - modifier.toRemoteCompose(), - textId, - colorInt, - colorId, - fontSizePx, - -1f, // minFontSize - -1f, // maxFontSize - fontStyle.toRemoteCompose(), - fontWeight?.weight?.toFloat() ?: 400f, - fontFamily.toRemoteCompose(), - textAlign.toRemoteCompose(), - overflow.toRemoteCompose(), - maxLines, - 0f, // letterSpacing - 0f, // lineHeightAdd - 1f, // lineHeightMultiply - 0, // lineBreakStrategy - 0, // hyphenationFrequency - 0, // justificationMode - textDecoration.contains(TextDecoration.Underline), - textDecoration.contains(TextDecoration.LineThrough), - null, // fontAxis - null, // fontAxisValues - false, // autosize - 0, // flags - { /* content runs children if any, but RemoteTextV2 is a leaf */ }, - ) + val (fontAxisNames, fontAxisValues) = + extractFontSettings(fontVariationSettings?.settings) + + val fontSizePx = fontSize.getFloatIdForCreationState(creationState) + + creationState.document.startTextComponent( + modifier.toRemoteCompose(), + textIdValue, + colorInt, + colorId, + fontSizePx, + minFontSize ?: -1f, + maxFontSize ?: -1f, + fontStyle.toRemoteCompose(), + fontWeight.getFloatIdForCreationState(creationState), + fontFamily, + textAlign.toRemoteCompose(), + overflow.toRemoteCompose(), + maxLines, + letterSpacing ?: 0f, + lineHeightAdd ?: 0f, + lineHeightMultiply ?: 1f, + 0, // lineBreakStrategy + 0, // hyphenationFrequency + 0, // justificationMode + textDecoration.contains(TextDecoration.Underline), + textDecoration.contains(TextDecoration.LineThrough), + fontAxisNames, + fontAxisValues, + false, // autosize + 0, // flags + ) + creationState.document.endTextComponent() + } else { + val textId = text.getIdForCreationState(creationState) + + val colorInt = color?.constantValue?.toArgb() ?: android.graphics.Color.BLACK + val colorId = + if (color?.hasConstantValue == false) { + color!!.getIdForCreationState(creationState) + } else { + -1 + } + + val colorValue = + color?.constantValue?.toArgb() + ?: (if (color?.hasConstantValue == false) { + color!!.getIdForCreationState(creationState) + } else { + android.graphics.Color.BLACK + }) + val flags = + if (color?.hasConstantValue == false) { + TextLayout.FLAG_IS_DYNAMIC_COLOR.toShort() + } else { + 0.toShort() + } + + val fontSizePx = fontSize.getFloatIdForCreationState(creationState) + creationState.document.startTextComponent( + modifier.toRemoteCompose(), + textId, + colorValue, + fontSizePx, + fontStyle.toRemoteCompose(), + fontWeight.constantValue ?: 400f, + fontFamily, + flags, + textAlign.toRemoteCompose().toShort(), + overflow.toRemoteCompose(), + maxLines, + ) + creationState.document.endTextComponent() + } } } @@ -216,6 +269,7 @@ private fun FontStyle?.toRemoteCompose(): Int = private fun FontFamily?.toRemoteCompose(): String? = when (this) { + null -> null FontFamily.Default -> "default" FontFamily.SansSerif -> "sans-serif" FontFamily.Serif -> "serif" diff --git a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteTextV2.kt b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteTextV2.kt index a8d9802ded755..62568f130d800 100644 --- a/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteTextV2.kt +++ b/compose/remote/remote-creation-compose/src/main/java/androidx/compose/remote/creation/compose/v2/RemoteTextV2.kt @@ -20,32 +20,35 @@ import androidx.annotation.RestrictTo import androidx.compose.remote.creation.compose.layout.RemoteComposable import androidx.compose.remote.creation.compose.modifier.RemoteModifier import androidx.compose.remote.creation.compose.state.RemoteColor +import androidx.compose.remote.creation.compose.state.RemoteFloat import androidx.compose.remote.creation.compose.state.RemoteString +import androidx.compose.remote.creation.compose.state.rf import androidx.compose.runtime.Composable -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontVariation -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.TextUnit @Composable @RemoteComposable @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun RemoteTextV2( - text: String? = null, - remoteText: RemoteString? = null, + text: RemoteString, modifier: RemoteModifier = RemoteModifier, color: RemoteColor? = null, - fontSize: TextUnit = TextUnit.Unspecified, - fontWeight: FontWeight? = null, + fontSize: RemoteFloat = 14f.rf, + fontWeight: RemoteFloat = 400f.rf, fontStyle: FontStyle? = null, - fontFamily: FontFamily? = null, + fontFamily: String? = null, textAlign: TextAlign? = null, overflow: TextOverflow = TextOverflow.Clip, maxLines: Int = Int.MAX_VALUE, + minFontSize: Float? = null, + maxFontSize: Float? = null, + letterSpacing: Float? = null, + lineHeightAdd: Float? = null, + lineHeightMultiply: Float? = null, textDecoration: TextDecoration = TextDecoration.None, fontVariationSettings: FontVariation.Settings? = null, ) { @@ -53,7 +56,6 @@ public fun RemoteTextV2( factory = { RemoteTextNodeV2() }, update = { set(text) { this.text = it } - set(remoteText) { this.remoteText = it } set(modifier) { this.modifier = it } set(color) { this.color = it } set(fontSize) { this.fontSize = it } @@ -63,6 +65,11 @@ public fun RemoteTextV2( set(textAlign) { this.textAlign = it } set(overflow) { this.overflow = it } set(maxLines) { this.maxLines = it } + set(minFontSize) { this.minFontSize = it } + set(maxFontSize) { this.maxFontSize = it } + set(letterSpacing) { this.letterSpacing = it } + set(lineHeightAdd) { this.lineHeightAdd = it } + set(lineHeightMultiply) { this.lineHeightMultiply = it } set(textDecoration) { this.textDecoration = it } set(fontVariationSettings) { this.fontVariationSettings = it } }, diff --git a/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/action/CombinedActionTest.kt b/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/action/CombinedActionTest.kt new file mode 100644 index 0000000000000..a1d7356645f89 --- /dev/null +++ b/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/action/CombinedActionTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2026 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.compose.remote.creation.compose.action + +import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.RemoteComposeWriter +import androidx.compose.remote.creation.actions.Action as CoreAction +import androidx.compose.remote.creation.profile.RcPlatformProfiles +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class CombinedActionTest { + @Test + fun toRemoteAction_delegatesToChildren() { + val recordedWrites = mutableListOf() + + val action1 = + object : Action { + override fun toRemoteAction(): CoreAction = CoreAction { + recordedWrites.add("action1") + } + } + + val action2 = + object : Action { + override fun toRemoteAction(): CoreAction = CoreAction { + recordedWrites.add("action2") + } + } + + val combinedAction = CombinedAction(action1, action2) + val remoteAction = combinedAction.toRemoteAction() + + // Use a real writer + val writer = + RemoteComposeWriter( + CreationDisplayInfo(100, 100, 160), + null, + RcPlatformProfiles.ANDROIDX, + null, + ) + + remoteAction.write(writer) + + assertThat(recordedWrites).containsExactly("action1", "action2").inOrder() + } +} diff --git a/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/v2/RemoteComposeV2Test.kt b/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/v2/RemoteComposeV2Test.kt index 11b4626cc5684..6538a6f801e4c 100644 --- a/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/v2/RemoteComposeV2Test.kt +++ b/compose/remote/remote-creation-compose/src/test/java/androidx/compose/remote/creation/compose/v2/RemoteComposeV2Test.kt @@ -14,9 +14,13 @@ * limitations under the License. */ +@file:OptIn(ExperimentalRemoteCreationComposeApi::class) + package androidx.compose.remote.creation.compose.v2 import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.compose.ExperimentalRemoteCreationComposeApi +import androidx.compose.remote.creation.compose.RemoteComposeCreationComposeFlags import androidx.compose.remote.creation.compose.layout.RemoteBox import androidx.compose.remote.creation.compose.layout.RemoteCanvas import androidx.compose.remote.creation.compose.layout.RemoteColumn @@ -25,15 +29,13 @@ import androidx.compose.remote.creation.compose.layout.RemoteText import androidx.compose.remote.creation.compose.modifier.RemoteModifier import androidx.compose.remote.creation.compose.state.RemoteColor import androidx.compose.remote.creation.compose.state.RemotePaint -import androidx.compose.runtime.BroadcastFrameClock +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.yield import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -43,81 +45,58 @@ import org.robolectric.annotation.Config @Config(sdk = [Config.TARGET_SDK]) class RemoteComposeV2Test { + @Before + fun setup() { + RemoteComposeCreationComposeFlags.isRemoteApplierEnabled = true + } + @Test fun testCaptureDocument() = runTest { val displayInfo = CreationDisplayInfo(500, 500, 1) - val clock = BroadcastFrameClock() - val flow = - captureRemoteDocumentV2(displayInfo, context = coroutineContext + clock) { - RemoteBoxV2 { RemoteTextV2(text = "Hello V2") } + val document = + captureSingleRemoteDocumentV2(displayInfo) { + RemoteBoxV2 { RemoteTextV2(text = "Hello V2".rs) } } - launch { - yield() - clock.sendFrame(0L) - yield() - clock.sendFrame(16_000_000L) - } // Trigger recomposition - - val document = flow.first() assertNotNull(document) - assertTrue(document.isNotEmpty()) + assertTrue(document.bytes.isNotEmpty()) } @Test fun testComplexComposition() = runTest { val displayInfo = CreationDisplayInfo(500, 500, 1) - val clock = BroadcastFrameClock() - val flow = - captureRemoteDocumentV2(displayInfo, context = coroutineContext + clock) { + val document = + captureSingleRemoteDocumentV2(displayInfo) { RemoteColumnV2 { - RemoteTextV2(text = "Item 1") - RemoteRowV2 { RemoteTextV2(text = "Nested Item") } + RemoteTextV2(text = "Item 1".rs) + RemoteRowV2 { RemoteTextV2(text = "Nested Item".rs) } } } - launch { - yield() - clock.sendFrame(0L) - yield() - clock.sendFrame(16_000_000L) - } // Trigger recomposition - - val document = flow.first() assertNotNull(document) - assertTrue(document.isNotEmpty()) + assertTrue(document.bytes.isNotEmpty()) } @Test fun testScopeAndSpacer() = runTest { val displayInfo = CreationDisplayInfo(500, 500, 1) - val clock = BroadcastFrameClock() - val flow = - captureRemoteDocumentV2(displayInfo, context = coroutineContext + clock) { + val document = + captureSingleRemoteDocumentV2(displayInfo) { RemoteRowV2 { RemoteSpacerV2(modifier = RemoteModifier.weight(1f)) - RemoteTextV2(text = "End") + RemoteTextV2(text = "End".rs) } } - launch { - yield() - clock.sendFrame(0L) - yield() - clock.sendFrame(16_000_000L) - } - - val document = flow.first() assertNotNull(document) - assertTrue(document.isNotEmpty()) + assertTrue(document.bytes.isNotEmpty()) } @Test fun testV1toV2Switching() = runTest { val displayInfo = CreationDisplayInfo(500, 500, 1) - val clock = BroadcastFrameClock() - val flow = - captureRemoteDocumentV2(displayInfo, context = coroutineContext + clock) { + val document = + captureSingleRemoteDocumentV2(displayInfo) { // Using V1 components inside V2 capture RemoteBox { RemoteColumn { @@ -127,24 +106,15 @@ class RemoteComposeV2Test { } } - launch { - yield() - clock.sendFrame(0L) - yield() - clock.sendFrame(16_000_000L) - } - - val document = flow.first() assertNotNull(document) - assertTrue(document.isNotEmpty()) + assertTrue(document.bytes.isNotEmpty()) } @Test fun testRemoteCanvasV2() = runTest { val displayInfo = CreationDisplayInfo(500, 500, 1) - val clock = BroadcastFrameClock() - val flow = - captureRemoteDocumentV2(displayInfo, context = coroutineContext + clock) { + val document = + captureSingleRemoteDocumentV2(displayInfo) { RemoteCanvas { drawRect( paint = @@ -153,15 +123,7 @@ class RemoteComposeV2Test { } } - launch { - yield() - clock.sendFrame(0L) - yield() - clock.sendFrame(16_000_000L) - } - - val document = flow.first() assertNotNull(document) - assertTrue(document.isNotEmpty()) + assertTrue(document.bytes.isNotEmpty()) } } diff --git a/compose/ui/ui-graphics/src/androidDeviceTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt b/compose/ui/ui-graphics/src/androidDeviceTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt index e077dbc141085..f7446998e7444 100644 --- a/compose/ui/ui-graphics/src/androidDeviceTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt +++ b/compose/ui/ui-graphics/src/androidDeviceTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt @@ -28,6 +28,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import androidx.test.filters.SmallTest import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assume.assumeFalse import org.junit.Test import org.junit.runner.RunWith @@ -272,6 +274,22 @@ class AndroidColorSpaceTest { ) } + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) + @Test + fun testConvertCustomTransform() { + val transform = + floatArrayOf(0.9642029f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.8249054f) + val transferParameters = + (ColorSpace.get(ColorSpace.Named.SRGB) as ColorSpace.Rgb).transferParameters!! + val androidColorSpace = ColorSpace.Rgb("custom", transform, transferParameters) + // On O and P, the transform doesn't stay, but on Q+, it works for grayscale transforms + assumeFalse(androidColorSpace.transform[0].isNaN()) + val composeColorSpace = androidColorSpace.toComposeColorSpace() + val rgb = composeColorSpace.toAndroidColorSpace() as android.graphics.ColorSpace.Rgb + val actualTransform = rgb.transform + transform.indices.forEach { i -> assertEquals(transform[i], actualTransform[i], 0.01f) } + } + // Helper class to avoid NoSuchClassExceptions being thrown when tests are run on an older // API level that does not understand ColorSpace APIs internal class ColorSpaceHelper { diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt index 8ca5be63f7f80..dcddccd56a202 100644 --- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt +++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt @@ -105,13 +105,28 @@ private object ColorSpaceVerificationHelper { } else { null } + val transform = this.transform if (androidTransferParams != null) { - android.graphics.ColorSpace.Rgb( - this.name, - this.primaries, - whitePointArray, - androidTransferParams, - ) + val directColorSpace = + android.graphics.ColorSpace.Rgb( + this.name, + this.primaries, + whitePointArray, + androidTransferParams, + ) + if (transform[0].isNaN()) { + directColorSpace + } else { + if (directColorSpace.transform.contentEquals(transform)) { + directColorSpace + } else { + android.graphics.ColorSpace.Rgb( + this.name, + transform, + androidTransferParams, + ) + } + } } else { android.graphics.ColorSpace.Rgb( this.name, diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt index 239a814392a8b..1e4eb99052523 100644 --- a/compose/ui/ui/api/current.txt +++ b/compose/ui/ui/api/current.txt @@ -153,7 +153,6 @@ package androidx.compose.ui { property @Deprecated public boolean isRemoveFocusedViewFixEnabled; property public boolean isRequestFocusOnNonFocusableFocusTargetEnabled; property public boolean isScrollCaptureCenteringEnabled; - property public boolean isSharedComposeViewContextEnabled; property public boolean isTraversableDelegatesFixEnabled; property public boolean isViewFocusFixEnabled; field public static final androidx.compose.ui.ComposeUiFlags INSTANCE; @@ -170,7 +169,6 @@ package androidx.compose.ui { field @Deprecated public static boolean isRemoveFocusedViewFixEnabled; field public static boolean isRequestFocusOnNonFocusableFocusTargetEnabled; field public static boolean isScrollCaptureCenteringEnabled; - field public static boolean isSharedComposeViewContextEnabled; field public static boolean isTraversableDelegatesFixEnabled; field public static boolean isViewFocusFixEnabled; } diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt index bf6637530e665..6a422333e6a04 100644 --- a/compose/ui/ui/api/restricted_current.txt +++ b/compose/ui/ui/api/restricted_current.txt @@ -153,7 +153,6 @@ package androidx.compose.ui { property @Deprecated public boolean isRemoveFocusedViewFixEnabled; property public boolean isRequestFocusOnNonFocusableFocusTargetEnabled; property public boolean isScrollCaptureCenteringEnabled; - property public boolean isSharedComposeViewContextEnabled; property public boolean isTraversableDelegatesFixEnabled; property public boolean isViewFocusFixEnabled; field public static final androidx.compose.ui.ComposeUiFlags INSTANCE; @@ -170,7 +169,6 @@ package androidx.compose.ui { field @Deprecated public static boolean isRemoveFocusedViewFixEnabled; field public static boolean isRequestFocusOnNonFocusableFocusTargetEnabled; field public static boolean isScrollCaptureCenteringEnabled; - field public static boolean isSharedComposeViewContextEnabled; field public static boolean isTraversableDelegatesFixEnabled; field public static boolean isViewFocusFixEnabled; } diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ComposeViewCreationBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ComposeViewCreationBenchmark.kt deleted file mode 100644 index ce99e2d9ffa3b..0000000000000 --- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ComposeViewCreationBenchmark.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2025 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.compose.ui.benchmark - -import android.widget.FrameLayout -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.testutils.ComposeTestCase -import androidx.compose.testutils.benchmark.ComposeBenchmarkRule -import androidx.compose.testutils.doFramesUntilNoChangesPending -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.viewinterop.AndroidView -import org.junit.Rule -import org.junit.Test - -class ComposeViewCreationBenchmark { - @get:Rule val benchmarkRule = ComposeBenchmarkRule() - - @Test - fun createComposeView() { - with(benchmarkRule) { - runBenchmarkFor({ JustCreateComposeViewTestCase() }) { - runOnUiThread { doFramesUntilNoChangesPending() } - measureRepeatedOnUiThread { - val containingView = getTestCase().containingView - val composeView = - ComposeView(containingView.context).also { - it.setContent { Box(Modifier.fillMaxSize()) } - } - containingView.addView(composeView) - recompose() - runWithMeasurementDisabled { containingView.removeAllViews() } - } - } - } - } - - private class JustCreateComposeViewTestCase : ComposeTestCase { - lateinit var containingView: FrameLayout - - @Composable - override fun Content() { - AndroidView(factory = { context -> FrameLayout(context).also { containingView = it } }) - } - } -} diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt index 238d4cb30d4d5..0cd30d25319a3 100644 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt +++ b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt @@ -21,7 +21,6 @@ package androidx.compose.ui import android.content.Context import android.graphics.Bitmap import android.os.Build -import android.os.Bundle import android.os.Handler import android.os.Looper import android.transition.TransitionManager @@ -42,7 +41,6 @@ import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Recomposer import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -91,7 +89,6 @@ import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.AndroidComposeView import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ComposeViewContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.RenderNodeApi23 @@ -110,13 +107,6 @@ import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import androidx.compose.ui.unit.toOffset -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import androidx.savedstate.SavedStateRegistry -import androidx.savedstate.SavedStateRegistryController -import androidx.savedstate.SavedStateRegistryOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress @@ -124,7 +114,6 @@ import com.google.common.truth.Truth import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -import kotlin.coroutines.CoroutineContext import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -360,52 +349,6 @@ class AndroidLayoutDrawTest { assertTrue(cameraDistanceApplied) } - private fun createAndroidComposeView( - activity: TestActivity, - coroutineContext: CoroutineContext, - ): AndroidComposeView { - val lifecycleOwner = - object : LifecycleOwner { - override val lifecycle: Lifecycle - get() = - object : Lifecycle() { - override val currentState: Lifecycle.State - get() = Lifecycle.State.RESUMED - - override fun addObserver(observer: LifecycleObserver) {} - - override fun removeObserver(observer: LifecycleObserver) {} - } - } - val savedStateRegistryOwner = - object : SavedStateRegistryOwner { - val lifecycleRegistry = LifecycleRegistry.createUnsafe(this) - private val controller = - SavedStateRegistryController.create(this).apply { performRestore(Bundle()) } - - init { - lifecycleRegistry.currentState = Lifecycle.State.RESUMED - } - - override val savedStateRegistry: SavedStateRegistry - get() = controller.savedStateRegistry - - override val lifecycle: LifecycleRegistry - get() = lifecycleRegistry - } - - return AndroidComposeView( - activity, - ComposeViewContext( - compositionContext = Recomposer(coroutineContext), - lifecycleOwner = lifecycleOwner, - savedStateRegistryOwner = savedStateRegistryOwner, - viewModelStoreOwner = null, - view = activity.window.decorView, - ), - ) - } - @RequiresApi(Build.VERSION_CODES.Q) private fun verifyRenderNode29CompositingStrategy( compositingStrategy: CompositingStrategy, @@ -414,7 +357,7 @@ class AndroidLayoutDrawTest { ): Boolean { val node = RenderNodeApi29( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ) @@ -432,7 +375,7 @@ class AndroidLayoutDrawTest { ): Boolean { val node = RenderNodeApi23( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ) @@ -449,7 +392,7 @@ class AndroidLayoutDrawTest { ): Boolean { val view = ViewLayer( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ), @@ -474,7 +417,7 @@ class AndroidLayoutDrawTest { // Verify that the internal render node has the camera distance property // given to the wrapper RenderNodeApi29( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ) @@ -488,7 +431,7 @@ class AndroidLayoutDrawTest { // Verify that the internal render node has the camera distance property // given to the wrapper RenderNodeApi23( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ) @@ -500,7 +443,7 @@ class AndroidLayoutDrawTest { private fun verifyViewLayerCameraDistance(cameraDistance: Float): Boolean { val layer = ViewLayer( - createAndroidComposeView( + AndroidComposeView( activity, Executors.newFixedThreadPool(3).asCoroutineDispatcher(), ), diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/ComposeViewContextMemoryLeakTest.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/ComposeViewContextMemoryLeakTest.kt deleted file mode 100644 index 6080f999fc95f..0000000000000 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/ComposeViewContextMemoryLeakTest.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2025 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.compose.ui - -import android.R -import android.app.Activity -import android.view.View -import android.widget.FrameLayout -import androidx.activity.ComponentActivity -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.text.BasicText -import androidx.compose.ui.platform.AndroidUiDispatcher -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ComposeViewContext -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.LargeTest -import kotlinx.coroutines.runBlocking -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@LargeTest -@RunWith(AndroidJUnit4::class) -class ComposeViewContextMemoryLeakTest { - - @get:Rule val activityScenarioRule = ActivityScenarioRule(ComponentActivity::class.java) - - @Test - fun composeViewContext_assertNoLeak() { - lateinit var activity: Activity - activityScenarioRule.scenario.onActivity { activity = it } - runBlocking(AndroidUiDispatcher.Main) { - val emptyView = View(activity) - activity.setContentView(emptyView) - MemoryLeakTest.loopAndVerifyMemory( - iterations = 400, - gcFrequency = 40, - ignoreFirstRun = true, - ) { - val composeViewContext = ComposeViewContext(activity.findViewById(R.id.content)) - val composeView = ComposeView(activity) - composeView.setContent { Column { repeat(3) { Box { BasicText("Hello") } } } } - composeView.createComposition(composeViewContext) - activity.setContentView(composeView) - - // After composing, we clear it. - activity.setContentView(emptyView) - } - } - } - - @Test - fun composeViewContextRemovedView_assertNoLeak() { - lateinit var activity: Activity - lateinit var parentView: FrameLayout - activityScenarioRule.scenario.onActivity { - activity = it - parentView = FrameLayout(activity) - activity.setContentView(parentView) - } - runBlocking(AndroidUiDispatcher.Main) { - MemoryLeakTest.loopAndVerifyMemory( - iterations = 400, - gcFrequency = 40, - ignoreFirstRun = true, - ) { - val composeView = ComposeView(activity) - parentView.addView(composeView) - composeView.setContent { Column { repeat(3) { Box { BasicText("Hello") } } } } - parentView.removeAllViews() - } - } - } - - @Test - fun composeViewContext_multipleViews_noLeak() { - lateinit var activity: Activity - lateinit var parentView: FrameLayout - activityScenarioRule.scenario.onActivity { - activity = it - parentView = FrameLayout(activity) - activity.setContentView(parentView) - } - runBlocking(AndroidUiDispatcher.Main) { - MemoryLeakTest.loopAndVerifyMemory( - iterations = 400, - gcFrequency = 40, - ignoreFirstRun = true, - ) { - repeat(2) { - val composeView = ComposeView(activity) - parentView.addView(composeView) - composeView.setContent { Column { repeat(3) { Box { BasicText("Hello") } } } } - } - parentView.removeAllViews() - } - } - } -} diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt index a3e4ec97f1027..caec8016cf489 100644 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt +++ b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt @@ -265,72 +265,70 @@ class MemoryLeakTest { } } - companion object { - /** - * Runs the given code in a loop for exactly [iterations] times and every [gcFrequency] it - * will force garbage collection and check the allocated heap size. Suspending so that we - * can briefly yield() to the dispatcher before collecting garbage so that event loop driven - * cleanup processes can run before we take measurements. - */ - suspend fun loopAndVerifyMemory( - iterations: Int, - gcFrequency: Int, - ignoreFirstRun: Boolean = false, - operationToPerform: suspend () -> Unit, - ) { - val rawStats = ArrayList(iterations / gcFrequency) - - // Collect data - repeat(iterations) { i -> - if (i % gcFrequency == 0) { - // Let any scheduled cleanup processes run before we take measurements - yield() - Runtime.getRuntime().let { - it.gc() // Run gc - rawStats.add(it.totalMemory() - it.freeMemory()) // Collect memory info - } + /** + * Runs the given code in a loop for exactly [iterations] times and every [gcFrequency] it will + * force garbage collection and check the allocated heap size. Suspending so that we can briefly + * yield() to the dispatcher before collecting garbage so that event loop driven cleanup + * processes can run before we take measurements. + */ + suspend fun loopAndVerifyMemory( + iterations: Int, + gcFrequency: Int, + ignoreFirstRun: Boolean = false, + operationToPerform: suspend () -> Unit, + ) { + val rawStats = ArrayList(iterations / gcFrequency) + + // Collect data + repeat(iterations) { i -> + if (i % gcFrequency == 0) { + // Let any scheduled cleanup processes run before we take measurements + yield() + Runtime.getRuntime().let { + it.gc() // Run gc + rawStats.add(it.totalMemory() - it.freeMemory()) // Collect memory info } - operationToPerform() - } - - fun Long.formatMemory(): String { - return NumberFormat.getNumberInstance(Locale.US).format(this / 1024) + " KiB" } + operationToPerform() + } - // Throw away the first run if needed - val memoryStats = if (ignoreFirstRun) rawStats.drop(1) else rawStats - val formattedStats = memoryStats.joinToString(", ") { it.formatMemory() } + fun Long.formatMemory(): String { + return NumberFormat.getNumberInstance(Locale.US).format(this / 1024) + " KiB" + } - // Verify that memory did not grow - val min = memoryStats.minOrNull() - val max = memoryStats.maxOrNull() + // Throw away the first run if needed + val memoryStats = if (ignoreFirstRun) rawStats.drop(1) else rawStats + val formattedStats = memoryStats.joinToString(", ") { it.formatMemory() } - if (min == null || max == null) { - throw AssertionError("Collected memory data are corrupted") - } + // Verify that memory did not grow + val min = memoryStats.minOrNull() + val max = memoryStats.maxOrNull() - // Check if every iteration the memory grew => that's a bad sign - val diffs = memoryStats.zipWithNext().map { (it.second - it.first) / 1024 } - val areAllDiffsGrowing = diffs.all { it > 0 } - if (areAllDiffsGrowing) { - throw AssertionError( - "Possible memory leak detected!. Memory kept " + - "increasing every step. Min: ${min.formatMemory()}, max: " + - "${max.formatMemory()}\nData: [$formattedStats]" - ) - } + if (min == null || max == null) { + throw AssertionError("Collected memory data are corrupted") + } - // Check if we have a significant diff across all the data - val diff = max - min - if (diff > 1024 * 1024) { // 1 MiB tolerance - throw AssertionError( - "Possible memory leak detected! Min: " + - "${min.formatMemory()}, max: ${max.formatMemory()}\n" + - "Data: [$formattedStats]" - ) - } + // Check if every iteration the memory grew => that's a bad sign + val diffs = memoryStats.zipWithNext().map { (it.second - it.first) / 1024 } + val areAllDiffsGrowing = diffs.all { it > 0 } + if (areAllDiffsGrowing) { + throw AssertionError( + "Possible memory leak detected!. Memory kept " + + "increasing every step. Min: ${min.formatMemory()}, max: " + + "${max.formatMemory()}\nData: [$formattedStats]" + ) + } - Log.i("MemoryTest", "Measured memory data: $formattedStats") + // Check if we have a significant diff across all the data + val diff = max - min + if (diff > 1024 * 1024) { // 1 MiB tolerance + throw AssertionError( + "Possible memory leak detected! Min: " + + "${min.formatMemory()}, max: ${max.formatMemory()}\n" + + "Data: [$formattedStats]" + ) } + + Log.i("MemoryTest", "Measured memory data: $formattedStats") } } diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/platform/ComposeViewTest.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/platform/ComposeViewTest.kt index d73d8c7540e31..21749e7f49848 100644 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/platform/ComposeViewTest.kt +++ b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/platform/ComposeViewTest.kt @@ -16,30 +16,12 @@ package androidx.compose.ui.platform -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.ComposeUiFlags -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier import androidx.compose.ui.test.TestActivity import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule import androidx.compose.ui.tests.R -import androidx.compose.ui.viewinterop.AndroidView -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentContainerView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -73,489 +55,4 @@ class ComposeViewTest { rule.runOnIdle { activity.setContentView(view) } rule.waitForIdle() } - - @Test - fun composeWithComposeViewContext() { - rule.runOnUiThread { rule.activity.setContentView(FrameLayout(rule.activity)) } - rule.waitForIdle() - val contentView = rule.activity.findViewById(android.R.id.content) - val composeViewContext = rule.runOnUiThread { ComposeViewContext(contentView) } - val composeView = ComposeView(rule.activity) - var text by mutableStateOf("Hello") - lateinit var view: View - lateinit var readText: String - rule.runOnUiThread { - composeView.setContent { - view = LocalView.current - readText = text - } - composeView.createComposition(composeViewContext) - } - rule.waitForIdle() - assertThat(view.parent).isEqualTo(composeView) - assertThat(readText).isEqualTo("Hello") - text = "World" - rule.waitForIdle() - assertThat(readText).isEqualTo("World") - } - - @Test - fun setComposeViewContextToNullStopsObserving() { - lateinit var composeViewContext: ComposeViewContext - rule.setContent { - val view = LocalView.current - composeViewContext = remember { ComposeViewContext(view) } - } - - lateinit var composeView: ComposeView - var isComposed = false - rule.runOnIdle { - composeView = ComposeView(rule.activity) - composeView.setContent { isComposed = true } - composeView.createComposition(composeViewContext) - } - - rule.waitForIdle() - assertThat(isComposed).isTrue() - assertThat(composeViewContext.viewCount).isEqualTo(1) - rule.runOnUiThread { - composeView.disposeComposition() - assertThat(composeViewContext.viewCount).isEqualTo(0) - - // Doing it a second time does nothing - composeView.disposeComposition() - assertThat(composeViewContext.viewCount).isEqualTo(0) - } - } - - @Test - fun detachingComposeViewWithComposeViewContextStopsObserving() { - lateinit var composeViewContext: ComposeViewContext - var addView by mutableStateOf(false) - lateinit var composeView: ComposeView - - rule.setContent { - val view = LocalView.current - composeViewContext = remember { ComposeViewContext(view) } - if (addView) { - AndroidView(factory = { composeView }) - } - } - - var isComposed = false - rule.runOnIdle { - composeView = ComposeView(rule.activity) - composeView.setContent { isComposed = true } - composeView.createComposition(composeViewContext) - } - - rule.waitForIdle() - assertThat(isComposed).isTrue() - assertThat(composeViewContext.viewCount).isEqualTo(1) - - // Add the View - addView = true - rule.waitForIdle() - - // Removing the View should stop the ComposeViewContext from observing - addView = false - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(0) - - // Disposing the composition doesn't need do anything to the ComposeViewContext - rule.runOnIdle { - composeView.disposeComposition() - assertThat(composeViewContext.viewCount).isEqualTo(0) - } - } - - @Test - fun reattachingComposeViewWithComposeViewContextStartsObserving() { - lateinit var composeViewContext: ComposeViewContext - var addView by mutableStateOf(true) - lateinit var composeView: ComposeView - var isComposed = false - - rule.setContent { - val view = LocalView.current - composeViewContext = remember { ComposeViewContext(view) } - if (addView) { - AndroidView( - factory = { - composeView = ComposeView(rule.activity) - composeView.setContent { isComposed = true } - composeView.createComposition(composeViewContext) - composeView - } - ) - } - } - - rule.waitForIdle() - assertThat(isComposed).isTrue() - assertThat(composeViewContext.viewCount).isEqualTo(1) - - // Removing the View should stop the ComposeViewContext from observing - addView = false - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(0) - - // Adding it back should start the ComposeViewContext observing - addView = true - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(1) - } - - @Test - fun multipleComposeViewsSharingComposeViewContext() { - lateinit var composeViewContext: ComposeViewContext - var addView1 by mutableStateOf(true) - var addView2 by mutableStateOf(true) - - rule.setContent { - val view = LocalView.current - composeViewContext = remember { ComposeViewContext(view) } - if (addView1) { - AndroidView( - factory = { - ComposeView(rule.activity).also { - it.setContent { Box(Modifier.fillMaxSize()) } - it.createComposition(composeViewContext) - } - } - ) - } - if (addView2) { - AndroidView( - factory = { - ComposeView(rule.activity).also { - it.setContent { Box(Modifier.fillMaxSize()) } - it.createComposition(composeViewContext) - } - } - ) - } - } - - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(2) - - addView1 = false - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(1) - - addView2 = false - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(0) - - addView1 = true - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(1) - } - - @Test - fun composeViewWithComposeViewContextDisposeCompositionRecompose() { - lateinit var composeView: ComposeView - lateinit var composeViewContext: ComposeViewContext - var addView by mutableStateOf(false) - var isComposed = false - - rule.setContent { - val view = LocalView.current - composeViewContext = remember { ComposeViewContext(view) } - if (addView) { - AndroidView(factory = { composeView }) - } - } - - rule.waitForIdle() - rule.runOnIdle { - composeView = - ComposeView(rule.activity).also { - it.setContent { isComposed = true } - it.createComposition(composeViewContext) - } - } - addView = true - rule.waitForIdle() - addView = false - rule.runOnIdle { - isComposed = false - composeView.disposeComposition() - } - - rule.waitForIdle() - assertThat(isComposed).isFalse() - addView = true - rule.waitForIdle() - assertThat(isComposed).isTrue() - } - - @Test - fun detachedComposition() { - lateinit var view: View - rule.setContent { view = LocalView.current } - rule.waitForIdle() - val composeView = rule.runOnUiThread { ComposeView(view.context) } - var isComposed by mutableStateOf(false) - rule.runOnUiThread { - composeView.setContent { Box { isComposed = true } } - composeView.createComposition(ComposeViewContext(view)) - } - rule.waitForIdle() - assertThat(isComposed).isTrue() - } - - @Test - fun setContentAfterCreateComposition() { - lateinit var view: View - rule.setContent { view = LocalView.current } - rule.waitForIdle() - val composeViewContext = rule.runOnUiThread { ComposeViewContext(view) } - val composeView = - rule.runOnUiThread { - ComposeView(view.context).also { it.createComposition(composeViewContext) } - } - var isComposed by mutableStateOf(false) - rule.runOnUiThread { composeView.setContent { Box { isComposed = true } } } - rule.waitForIdle() - assertThat(isComposed).isTrue() - } - - @Test - fun reuseAutomaticComposeViewContext() { - lateinit var view: View - var addComposeView by mutableStateOf(false) - lateinit var composeView: ComposeView - rule.setContent { - view = LocalView.current - if (addComposeView) { - AndroidView(factory = { composeView }) - } - } - rule.waitForIdle() - val composeViewContext = rule.runOnUiThread { view.findViewTreeComposeViewContext()!! } - var isComposed by mutableStateOf(false) - composeView = - rule.runOnUiThread { - ComposeView(view.context).also { - it.createComposition(composeViewContext) - it.setContent { isComposed = true } - } - } - rule.waitForIdle() - assertThat(isComposed).isTrue() - assertThat(composeViewContext.viewCount).isEqualTo(2) - - // Adding the ComposeView to the hierarchy shouldn't add to the view count - addComposeView = true - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(2) - - // clear the hierarchy - rule.runOnUiThread { rule.activity.setContentView(View(rule.activity)) } - rule.waitForIdle() - assertThat(composeViewContext.viewCount).isEqualTo(0) - } - - @Test - fun disposedComposeViewContextCanRecompose() { - lateinit var view: View - var addComposeView by mutableStateOf(false) - lateinit var composeView: ComposeView - var recomposeInt by mutableIntStateOf(1) - rule.setContent { - view = LocalView.current - if (recomposeInt > 0 && addComposeView) { - AndroidView(factory = { composeView }) - } - } - rule.waitForIdle() - val composeViewContext = rule.runOnUiThread { view.findViewTreeComposeViewContext()!! } - var isComposed by mutableStateOf(false) - composeView = - rule.runOnUiThread { - ComposeView(view.context).also { - it.createComposition(composeViewContext) - it.setContent { isComposed = true } - } - } - rule.waitForIdle() - // Adding the ComposeView to the hierarchy shouldn't add to the view count - addComposeView = true - rule.runOnUiThread { - isComposed = false - composeView.disposeComposition() - // now force recomposition - recomposeInt++ - } - - rule.waitForIdle() - assertThat(isComposed).isTrue() - assertThat(composeViewContext.viewCount).isEqualTo(2) - } - - @Test - fun findViewTreeComposeViewContextExists() { - lateinit var view: View - rule.setContent { view = LocalView.current } - rule.waitForIdle() - assertThat(view.findViewTreeComposeViewContext()) - .isEqualTo((view as AndroidComposeView).composeViewContext) - } - - @Test - fun findViewTreeComposeViewContextSiblings() { - lateinit var view1: View - lateinit var view2: View - rule.runOnUiThread { - view1 = ComposeView(rule.activity) - view2 = ComposeView(rule.activity) - val group = FrameLayout(rule.activity) - group.addView(view1) - group.addView(view2) - rule.activity.setContentView(group) - } - rule.waitForIdle() - assertThat(view1.findViewTreeComposeViewContext()) - .isEqualTo(view2.findViewTreeComposeViewContext()) - } - - @Test - fun findViewTreeComposeViewContextChild() { - lateinit var view1: View - lateinit var view2: View - rule.setContent { - view1 = LocalView.current - AndroidView( - factory = { - view2 = ComposeView(it) - view2 - } - ) - } - rule.waitForIdle() - assertThat(view1.findViewTreeComposeViewContext()) - .isEqualTo(view2.findViewTreeComposeViewContext()) - } - - @Test - fun findViewTreeComposeViewContextDoesNotExists() { - val view = rule.activity.findViewById(android.R.id.content) - assertThat(view.findViewTreeComposeViewContext()).isNull() - } - - @Test - fun findViewTreeComposeViewContextLifecycleDifferent() { - lateinit var outer: View - rule.setContent { - outer = LocalView.current - AndroidView( - factory = { FragmentContainerView(it).also { it.id = R.id.lifecycleContainer } } - ) - } - class MyFragment : Fragment() { - lateinit var inner: ViewGroup - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - return FrameLayout(inflater.context).also { inner = it } - } - } - val fragment = MyFragment() - rule.runOnIdle { - with(rule.activity) { - supportFragmentManager - .beginTransaction() - .add(R.id.lifecycleContainer, fragment) - .commit() - } - } - rule.runOnIdle { - assertThat(fragment.inner.findViewTreeComposeViewContext()).isNull() - fragment.inner.addView( - ComposeView(rule.activity).also { it.setContent { Box(Modifier.fillMaxSize()) } } - ) - } - rule.runOnIdle { - assertThat(fragment.inner.findViewTreeComposeViewContext()).isNotNull() - assertThat(fragment.inner.findViewTreeComposeViewContext()) - .isNotEqualTo(outer.findViewTreeComposeViewContext()) - } - } - - @Test - fun composeViewContextViewProperty() { - lateinit var view: View - rule.setContent { view = LocalView.current } - rule.runOnIdle { - assertThat(view.composeViewContext).isNull() - val composeViewContext = view.findViewTreeComposeViewContext()!! - assertThat(composeViewContext).isNotNull() - view.composeViewContext = composeViewContext - assertThat(view.composeViewContext).isSameInstanceAs(composeViewContext) - } - } - - @Test - fun reuseComposeViewWithComposeViewContext() { - lateinit var view: View - lateinit var childView: ComposeView - var addView by mutableStateOf(false) - rule.setContent { - view = LocalView.current - Box(Modifier.fillMaxSize()) - if (addView) { - AndroidView(factory = { childView }) - } - } - val composeViewContext = rule.runOnUiThread { view.findViewTreeComposeViewContext()!! } - childView = - rule.runOnUiThread { - ComposeView(rule.activity).also { - it.setContent { Box(Modifier.fillMaxSize()) } - it.createComposition(composeViewContext) - } - } - rule.waitForIdle() - addView = true - rule.waitForIdle() - addView = false - rule.waitForIdle() - - // Now that the ComposeView has been added and removed, if we call setContent on it again, - // it should compose - var isComposed = false - childView.setContent { isComposed = true } - rule.waitForIdle() - assertThat(isComposed).isTrue() - } - - @Test - @OptIn(ExperimentalComposeUiApi::class) - fun frameRateCategoryViewProtection() { - ComposeUiFlags.isAdaptiveRefreshRateEnabled = false - - var addComposeView by mutableStateOf(true) - var isAdded by mutableStateOf(false) - rule.setContent { - if (addComposeView) { - AndroidView( - factory = { - ComposeView(it).apply { setContent { Box(Modifier.fillMaxSize()) } } - } - ) - } - isAdded = addComposeView - } - - rule.waitForIdle() - ComposeUiFlags.isAdaptiveRefreshRateEnabled = true - addComposeView = false - rule.waitForIdle() - assertThat(isAdded).isFalse() - } } diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/test/TestActivity.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/test/TestActivity.kt index 80b32e906dfef..7d705385f6ad1 100644 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/test/TestActivity.kt +++ b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/test/TestActivity.kt @@ -17,11 +17,11 @@ package androidx.compose.ui.test import android.os.Build import android.view.KeyEvent +import androidx.activity.ComponentActivity import androidx.compose.ui.platform.ViewLayer -import androidx.fragment.app.FragmentActivity import java.util.concurrent.CountDownLatch -open class TestActivity : FragmentActivity() { +open class TestActivity : ComponentActivity() { var receivedKeyEvent: KeyEvent? = null var hasFocusLatch = CountDownLatch(1) diff --git a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt index 47d5f7305b691..e4a8420a77e59 100644 --- a/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt +++ b/compose/ui/ui/src/androidDeviceTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt @@ -19,6 +19,7 @@ package androidx.compose.ui.viewinterop import android.content.Context import android.content.res.Configuration import android.os.Build +import android.util.DisplayMetrics import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup @@ -43,7 +44,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.Recomposer import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -56,12 +56,9 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.AbstractComposeView import androidx.compose.ui.platform.AndroidComposeView import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ComposeViewContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.platform.compositionContext -import androidx.compose.ui.platform.findViewTreeComposeViewContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule @@ -90,7 +87,6 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.math.roundToInt import kotlin.test.assertNotEquals -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.StandardTestDispatcher import org.hamcrest.CoreMatchers.instanceOf import org.junit.Assert.assertEquals @@ -786,8 +782,11 @@ class ComposeViewTest { assertEquals(composeView.width, with(density) { 100.dp.roundToPx() }) rule.activity.resources.displayMetrics.density = newDensity - rule.activity.resources.configuration.densityDpi = (newDensity * 160).roundToInt() - val newConfig = Configuration().apply { setTo(rule.activity.resources.configuration) } + val newConfig = + Configuration().apply { + setTo(rule.activity.resources.configuration) + densityDpi = (newDensity * DisplayMetrics.DENSITY_DEFAULT).roundToInt() + } composeView.dispatchConfigurationChanged(newConfig) } @@ -801,35 +800,6 @@ class ComposeViewTest { rule.activity.resources.displayMetrics.density = density.density } } - - @Test - fun composeView_changeComposeViewContext() { - rule.setContent { Box(Modifier.fillMaxSize()) } - - val composeView = - rule.runOnUiThread { - rule.activity.findViewById(android.R.id.content).getChildAt(0) - as ComposeView - } - - val originalComposeViewContext = composeView.findViewTreeComposeViewContext() - assertNotNull(originalComposeViewContext) - val originalCompositionContext = originalComposeViewContext!!.compositionContext - - rule.runOnUiThread { - composeView.composeViewContext = - ComposeViewContext( - view = originalComposeViewContext.view, - compositionContext = Recomposer(Dispatchers.Main), - lifecycleOwner = originalComposeViewContext.lifecycleOwner, - savedStateRegistryOwner = originalComposeViewContext.savedStateRegistryOwner, - viewModelStoreOwner = originalComposeViewContext.viewModelStoreOwner, - ) - } - val androidComposeViewContext = composeView.getChildAt(0) as AndroidComposeView - assertNotEquals(originalComposeViewContext, androidComposeViewContext.composeViewContext) - assertNotEquals(originalCompositionContext, androidComposeViewContext.compositionContext) - } } private const val SCROLLABLE_TAG = "scrollable" diff --git a/compose/ui/ui/src/androidHostTest/kotlin/androidx/compose/ui/platform/WindowInfoDeviceChangeTest.kt b/compose/ui/ui/src/androidHostTest/kotlin/androidx/compose/ui/platform/WindowInfoDeviceChangeTest.kt deleted file mode 100644 index e95e65d860e5e..0000000000000 --- a/compose/ui/ui/src/androidHostTest/kotlin/androidx/compose/ui/platform/WindowInfoDeviceChangeTest.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2025 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.compose.ui.platform - -import android.app.Activity -import android.content.pm.ActivityInfo -import android.content.res.Configuration -import android.graphics.Rect -import android.util.DisplayMetrics -import android.view.WindowManager -import androidx.activity.ComponentActivity -import androidx.compose.ui.unit.IntSize -import com.google.common.truth.Truth.assertThat -import java.util.concurrent.CountDownLatch -import kotlin.math.roundToInt -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment -import org.robolectric.Shadows -import org.robolectric.annotation.Config -import org.robolectric.shadows.ShadowDisplay -import org.robolectric.shadows.ShadowLooper - -@RunWith(RobolectricTestRunner::class) -@Config(minSdk = 31) -class WindowInfoDeviceChangeTest { - @Test - fun containerSizeUpdatesWhenDeviceSizeChanges() { - val controller = Robolectric.buildActivity(ComponentActivity::class.java).setup() - val activity = controller.get() - - var containerSize = IntSize.Zero - val composeView = - activity.runOnUiThreadBlocking { - ComposeView(activity).also { - it.setContent { containerSize = LocalWindowInfo.current.containerSize } - } - } - activity.runOnUiThreadBlocking { activity.setContentView(composeView) } - - activity.runOnUiThreadBlocking { assertThat(containerSize).isNotEqualTo(IntSize.Zero) } - - val shadowDisplay = Shadows.shadowOf(ShadowDisplay.getDefaultDisplay()) - - shadowDisplay.setWidth(1000) - shadowDisplay.setHeight(2000) - val configuration = Configuration(activity.applicationContext.resources.configuration) - configuration.screenWidthDp = - (1000 / activity.resources.displayMetrics.density).roundToInt() - configuration.screenHeightDp = - (2000 / activity.resources.displayMetrics.density).roundToInt() - configuration.smallestScreenWidthDp = 1000 - configuration.setWindowSize(1000, 2000) - - val displayMetrics = DisplayMetrics() - displayMetrics.setTo(activity.resources.displayMetrics) - displayMetrics.widthPixels = 1000 - displayMetrics.heightPixels = 2000 - - @Suppress("DEPRECATION") - activity.resources.updateConfiguration(configuration, displayMetrics) - - val activityInfo = - RuntimeEnvironment.getApplication() - .packageManager - .getActivityInfo(activity.componentName, 0) - activityInfo.configChanges = - activityInfo.configChanges or - ActivityInfo.CONFIG_SCREEN_SIZE or - ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE or - ActivityInfo.CONFIG_SCREEN_LAYOUT or - ActivityInfo.CONFIG_ORIENTATION or - ActivityInfo.CONFIG_DENSITY - val shadowPackageManager = - Shadows.shadowOf(RuntimeEnvironment.getApplication().packageManager) - shadowPackageManager.addOrUpdateActivity(activityInfo) - - activity.resources.configuration.updateFrom(configuration) - activity.application.resources.configuration.updateFrom(configuration) - val windowManager = activity.applicationContext.getSystemService(WindowManager::class.java) - - controller.configurationChange(configuration, displayMetrics) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - activity.runOnUiThreadBlocking { assertThat(containerSize).isEqualTo(IntSize(1000, 2000)) } - } - - inline fun Activity.runOnUiThreadBlocking(crossinline block: () -> T): T { - val latch = CountDownLatch(1) - var result: T? = null - runOnUiThread { - result = block() - latch.countDown() - } - latch.await() - @Suppress("UNCHECKED_CAST") - return result as T - } - - /** - * There's no public API to change the window size set in a Configuration, so we have to use - * reflection to get access to it. - */ - fun Configuration.setWindowSize(width: Int, height: Int) { - val windowConfigField = Configuration::class.java.getDeclaredField("windowConfiguration") - windowConfigField.isAccessible = true - val windowConfiguration = windowConfigField.get(this) - val windowConfigurationClass = Class.forName("android.app.WindowConfiguration") - val setBoundsMethod = - windowConfigurationClass.getDeclaredMethod("setBounds", Rect::class.java) - val bounds = Rect(0, 0, width, height) - setBoundsMethod.invoke(windowConfiguration, bounds) - val setAppBoundsMethod = - windowConfigurationClass.getDeclaredMethod("setAppBounds", Rect::class.java) - setAppBoundsMethod.invoke(windowConfiguration, bounds) - val setMaxBoundsMethod = - windowConfigurationClass.getDeclaredMethod("setMaxBounds", Rect::class.java) - setMaxBoundsMethod.invoke(windowConfiguration, bounds) - } -} diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt index d786f4655551e..581ed0a00fe58 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt @@ -20,6 +20,7 @@ package androidx.compose.ui.platform import android.annotation.SuppressLint import android.content.Context +import android.content.pm.ActivityInfo import android.content.res.Configuration import android.graphics.Point import android.graphics.Rect @@ -78,6 +79,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.referentialEqualityPolicy import androidx.compose.runtime.retain.ForgetfulRetainedValuesStore import androidx.compose.runtime.retain.RetainedValuesStore import androidx.compose.runtime.setValue @@ -127,6 +129,7 @@ import androidx.compose.ui.focus.toLayoutDirection import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.CanvasHolder import androidx.compose.ui.graphics.GraphicsContext import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.drawscope.DrawScope @@ -135,6 +138,7 @@ import androidx.compose.ui.graphics.setFrom import androidx.compose.ui.graphics.toAndroidRect import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.hapticfeedback.HapticFeedback +import androidx.compose.ui.hapticfeedback.PlatformHapticFeedback import androidx.compose.ui.input.InputMode.Companion.Keyboard import androidx.compose.ui.input.InputMode.Companion.Touch import androidx.compose.ui.input.InputModeManager @@ -180,6 +184,7 @@ import androidx.compose.ui.node.InternalCoreApi import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.LayoutNode import androidx.compose.ui.node.LayoutNode.UsageByParent +import androidx.compose.ui.node.LayoutNodeDrawScope import androidx.compose.ui.node.MeasureAndLayoutDelegate import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.Nodes @@ -206,6 +211,7 @@ import androidx.compose.ui.semantics.findClosestParentNode import androidx.compose.ui.spatial.RectManager import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.createFontFamilyResolver import androidx.compose.ui.text.input.PlatformTextInputService import androidx.compose.ui.text.input.TextInputService import androidx.compose.ui.text.input.TextInputServiceAndroid @@ -237,7 +243,12 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.get +import androidx.savedstate.SavedStateRegistryOwner +import androidx.savedstate.findViewTreeSavedStateRegistryOwner import java.lang.reflect.Method import java.util.function.Consumer import kotlin.coroutines.CoroutineContext @@ -254,7 +265,7 @@ private const val ONE_FRAME_120_HERTZ_IN_MILLISECONDS = 8L @Suppress("ViewConstructor", "VisibleForTests") @OptIn(InternalComposeUiApi::class) -internal class AndroidComposeView(context: Context, composeViewContext: ComposeViewContext) : +internal class AndroidComposeView(context: Context, coroutineContext: CoroutineContext) : ViewGroup(context), Owner, PlatformFocusOwner, @@ -267,45 +278,6 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV ViewTreeObserver.OnTouchModeChangeListener, FocusListener { - // We must access the value to decrement the View count on the ComposeViewContext when setting - // the composeViewContext. This is a copy of _composeViewContext that doesn't require triggering - // a state read while setting the value. - private var nonStateReadComposeViewContext: ComposeViewContext = composeViewContext - private var _composeViewContext by mutableStateOf(composeViewContext) - var composeViewContext: ComposeViewContext - get() = _composeViewContext - set(value) { - val hasCoroutineContextChanged = - coroutineContext != value.compositionContext.effectCoroutineContext - if (isAttachedToWindow) { - nonStateReadComposeViewContext.decrementViewCount() - value.incrementViewCount() - } - _composeViewContext = value - nonStateReadComposeViewContext = value - // In some rare cases, the CoroutineContext is cancelled (because the parent - // CompositionContext containing the CoroutineContext is no longer associated with this - // class). Changing this CoroutineContext to the new CompositionContext's - // CoroutineContext needs to cancel all Pointer Input Nodes relying on the old - // CoroutineContext. See [Wrapper.android.kt] for more details. - if (hasCoroutineContextChanged) { - val headModifierNode = root.nodes.head - - // Reset head Modifier.Node's pointer input handler (that is, the underlying - // coroutine used to run the handler for input pointer events). - if (headModifierNode is SuspendingPointerInputModifierNode) { - headModifierNode.resetPointerInputHandler() - } - - // Reset all other Modifier.Node's pointer input handler in the chain. - headModifierNode.visitSubtree(Nodes.PointerInput) { - if (it is SuspendingPointerInputModifierNode) { - it.resetPointerInputHandler() - } - } - } - } - /** * Remembers the position of the last pointer input event that was down. This position will be * used to calculate whether this view is considered scrollable via [canScrollHorizontally]/ @@ -330,8 +302,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV IndirectPointerEventPrimaryDirectionalMotionAxis? = null - override val sharedDrawScope - get() = composeViewContext.sharedDrawScope + override val sharedDrawScope = LayoutNodeDrawScope() override val view: View get() = this @@ -357,14 +328,16 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } } - override val density by composeViewContext.density + override var density by mutableStateOf(Density(context), referentialEqualityPolicy()) + private set private var frameRateCategoryView: View? = null - internal val isArrEnabled: Boolean - get() = - @OptIn(ExperimentalComposeUiApi::class) isAdaptiveRefreshRateEnabled && - SDK_INT >= VANILLA_ICE_CREAM + internal val isArrEnabled = + @OptIn(ExperimentalComposeUiApi::class) isAdaptiveRefreshRateEnabled && + SDK_INT >= VANILLA_ICE_CREAM + + private val rootSemanticsNode = EmptySemanticsModifier() override val focusOwner: FocusOwner = FocusOwnerImpl(this, this) @@ -372,19 +345,36 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV return IMPORTANT_FOR_AUTOFILL_YES } - override val coroutineContext: CoroutineContext - get() = composeViewContext.compositionContext.effectCoroutineContext + override var coroutineContext: CoroutineContext = coroutineContext + // In some rare cases, the CoroutineContext is cancelled (because the parent + // CompositionContext containing the CoroutineContext is no longer associated with this + // class). Changing this CoroutineContext to the new CompositionContext's CoroutineContext + // needs to cancel all Pointer Input Nodes relying on the old CoroutineContext. + // See [Wrapper.android.kt] for more details. + set(value) { + field = value + + val headModifierNode = root.nodes.head + + // Reset head Modifier.Node's pointer input handler (that is, the underlying + // coroutine used to run the handler for input pointer events). + if (headModifierNode is SuspendingPointerInputModifierNode) { + headModifierNode.resetPointerInputHandler() + } + + // Reset all other Modifier.Node's pointer input handler in the chain. + headModifierNode.visitSubtree(Nodes.PointerInput) { + if (it is SuspendingPointerInputModifierNode) { + it.resetPointerInputHandler() + } + } + } override val dragAndDropManager = AndroidDragAndDropManager(::startDrag) + private val _windowInfo: LazyWindowInfo = LazyWindowInfo() override val windowInfo: WindowInfo - get() = composeViewContext.windowInfo - - // This is only needed because the existing XR implementation is lacking. It is currently - // relying on the derivedStateOf() notification change. This can be removed when - // b/442011315 is fixed. - private var isAttached by mutableStateOf(false) - private val derivedIsAttached by derivedStateOf { isAttached } + get() = _windowInfo /** * Because AndroidComposeView always accepts focus, we have to divert focus to another View if @@ -550,8 +540,10 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV return null } - override val viewConfiguration: ViewConfiguration - get() = composeViewContext.viewConfiguration + private val canvasHolder = CanvasHolder() + + override val viewConfiguration: ViewConfiguration = + AndroidViewConfiguration(android.view.ViewConfiguration.get(context)) val insetsListener = InsetsListener(this) @@ -584,17 +576,11 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV override val rectManager = RectManager(layoutNodes) - override val rootForTest: RootForTest - get() = this - - internal var uncaughtExceptionHandler: RootForTest.UncaughtExceptionHandler? - get() = composeViewContext.uncaughtExceptionHandler - set(value) { - composeViewContext.uncaughtExceptionHandler = value - } + override val rootForTest: RootForTest = this + internal var uncaughtExceptionHandler: RootForTest.UncaughtExceptionHandler? = null override val semanticsOwner: SemanticsOwner = - SemanticsOwner(root, EmptySemanticsModifier(), layoutNodes) + SemanticsOwner(root, rootSemanticsNode, layoutNodes) private val composeAccessibilityDelegate = AndroidComposeViewAccessibilityDelegateCompat(this) internal var contentCaptureManager = AndroidContentCaptureManager( @@ -605,8 +591,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV /** * Provide accessibility manager to the user. Use the Android version of accessibility manager. */ - override val accessibilityManager - get() = composeViewContext.accessibilityManager + override val accessibilityManager = AndroidAccessibilityManager(context) /** * Provide access to a GraphicsContext instance used to create GraphicsLayers for providing @@ -634,9 +619,14 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV private val motionEventAdapter = MotionEventAdapter() private val pointerInputEventProcessor = PointerInputEventProcessor(root) + /** + * The snapshot-state backed current [Configuration]. This is updated by [updateConfiguration]. + */ + var configuration: Configuration by + mutableStateOf(Configuration(context.resources.configuration)) + override val localeList: LocaleList by derivedStateOf { - val platformLocaleListCompat = - ConfigurationCompat.getLocales(composeViewContext.configuration.value) + val platformLocaleListCompat = ConfigurationCompat.getLocales(configuration) LocaleList(List(platformLocaleListCompat.size()) { Locale(platformLocaleListCompat[it]!!) }) } @@ -668,11 +658,9 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV private var observationClearRequested = false /** Provide clipboard manager to the user. Use the Android version of clipboard manager. */ - override val clipboardManager - get() = composeViewContext.clipboardManager + override val clipboardManager = AndroidClipboardManager(context) - override val clipboard - get() = composeViewContext.clipboard + override val clipboard = AndroidClipboard(clipboardManager) override val snapshotObserver = OwnerSnapshotObserver { command -> val exceptionHandler = uncaughtExceptionHandler @@ -689,10 +677,10 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV command } - if (view.handler?.looper === Looper.myLooper()) { + if (handler?.looper === Looper.myLooper()) { command() } else { - view.handler?.post(command) + handler?.post(command) } } @@ -758,37 +746,33 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV // so that we don't have to continue using try/catch after fails once. private var isRenderNodeCompatible = true - private var onReadyForComposition: ((ComposeViewContext) -> Unit)? = null + private var _viewTreeOwners: ViewTreeOwners? by mutableStateOf(null) - private var _legacyTextInputServiceAndroid: TextInputServiceAndroid? = null - private val legacyTextInputServiceAndroid: TextInputServiceAndroid - get() = - _legacyTextInputServiceAndroid - ?: TextInputServiceAndroid(view, this).also { _legacyTextInputServiceAndroid = it } + // Having an extra derived state here (instead of directly using _viewTreeOwners) is a + // workaround for b/271579465 to avoid unnecessary extra recompositions when this is mutated + // before setContent is called. + /** + * Current [ViewTreeOwners]. Use [setOnViewTreeOwnersAvailable] if you want to execute your code + * when the object will be created. + */ + val viewTreeOwners: ViewTreeOwners? by derivedStateOf { _viewTreeOwners } + + private var onViewTreeOwnersAvailable: ((ViewTreeOwners) -> Unit)? = null + + private val legacyTextInputServiceAndroid = TextInputServiceAndroid(view, this) - private var _textInputService: TextInputService? = null /** * The legacy text input service. This is only used for new text input sessions if * [textInputSessionMutex] is null. */ @Deprecated("Use PlatformTextInputModifierNode instead.") - override val textInputService: TextInputService - get() = - _textInputService - ?: TextInputService( - platformTextInputServiceInterceptor(legacyTextInputServiceAndroid) - ) - .also { _textInputService = it } + override val textInputService = + TextInputService(platformTextInputServiceInterceptor(legacyTextInputServiceAndroid)) private val textInputSessionMutex = SessionMutex() - private var _softwareKeyboardController: SoftwareKeyboardController? = null - override val softwareKeyboardController: SoftwareKeyboardController - get() = - _softwareKeyboardController - ?: DelegatingSoftwareKeyboardController(textInputService).also { - _softwareKeyboardController = it - } + override val softwareKeyboardController: SoftwareKeyboardController = + DelegatingSoftwareKeyboardController(textInputService) override val placementScope: Placeable.PlacementScope get() = PlacementScope(this) @@ -812,12 +796,16 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV replaceWith = ReplaceWith("fontFamilyResolver"), ) @Suppress("DEPRECATION") - override val fontLoader: Font.ResourceLoader - get() = composeViewContext.fontLoader + override val fontLoader: Font.ResourceLoader = AndroidFontResourceLoader(context) // Backed by mutableStateOf so that the local provider recomposes when it changes // FontFamily.Resolver is not guaranteed to be stable or immutable, hence referential check - override val fontFamilyResolver: FontFamily.Resolver by composeViewContext.fontFamilyResolver + override var fontFamilyResolver: FontFamily.Resolver by + mutableStateOf(createFontFamilyResolver(context), referentialEqualityPolicy()) + private set + + private val Configuration.fontWeightAdjustmentCompat: Int + get() = if (SDK_INT >= S) fontWeightAdjustment else 0 // Backed by mutableStateOf so that the ambient provider recomposes when it changes override var layoutDirection by @@ -833,8 +821,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV private set /** Provide haptic feedback to the user. Use the Android version of haptic feedback. */ - override val hapticFeedBack: HapticFeedback - get() = composeViewContext.hapticFeedback + override val hapticFeedBack: HapticFeedback = PlatformHapticFeedback(this) /** Provide an instance of [InputModeManager] which is available as a CompositionLocal. */ private val _inputModeManager = @@ -968,14 +955,6 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV */ private var keyboardModifiersRequireUpdate = false - /** - * Used to track when [composeViewContext] calls [ComposeViewContext.incrementViewCount] during - * init. This is needed because once this has been added to the hierarchy, we don't need to - * specially treat it with respect to [ComposeViewContext]. It will stop composing when detached - * from the hierarchy. - */ - internal var composeViewContextIncrementedDuringInit = false - init { addOnAttachStateChangeListener(contentCaptureManager) setWillNotDraw(false) @@ -1009,18 +988,6 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } } - /** - * Called when [AbstractComposeView.composeViewContext] is set to `null`. This will remove the - * attachment of this AndroidComposeView from the ComposeViewContext so that it can stop - * observing when no AndroidComposeViews need it. - */ - fun removeConnectionToComposeViewContext() { - if (composeViewContextIncrementedDuringInit) { - composeViewContext.decrementViewCount() - composeViewContextIncrementedDuringInit = false - } - } - /** * Since this view has its own concept of internal focus, it needs to report that to the view * system for accurate focus searching and so ViewRootImpl will scroll correctly. @@ -1373,6 +1340,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } override fun onWindowFocusChanged(hasWindowFocus: Boolean) { + _windowInfo.isWindowFocused = hasWindowFocus keyboardModifiersRequireUpdate = true super.onWindowFocusChanged(hasWindowFocus) @@ -1417,8 +1385,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV if (isFocused) { // Focus lies within the Compose hierarchy, so we dispatch the key event to the // appropriate place. - composeViewContext.windowInfo.keyboardModifiers = - PointerKeyboardModifiers(event.metaState) + _windowInfo.keyboardModifiers = PointerKeyboardModifiers(event.metaState) // If the event is not consumed, use the default implementation. focusOwner.dispatchKeyEvent(KeyEvent(event)) || super.dispatchKeyEvent(event) } else { @@ -2102,7 +2069,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV // that will observe all children. The AndroidComposeView has only the // root, so it doesn't have to invalidate itself based on model changes. try { - composeViewContext.canvasHolder.drawInto(canvas) { + canvasHolder.drawInto(canvas) { root.draw( canvas = this, graphicsLayer = null, // the root node will provide the root graphics layer @@ -2180,18 +2147,16 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } /** - * The callback to be executed when the View is attached to the window or a custom - * [ComposeViewContext] is used. Note that this callback will be fired inline if it is already - * ready. + * The callback to be executed when [viewTreeOwners] is created and not-null anymore. Note that + * this callback will be fired inline when it is already available */ - fun setOnReadyForComposition(callback: (ComposeViewContext) -> Unit) { - // Use a derivedStateOf so that the caller is notified when the attachment state - // changes. This is relied on by XR. - derivedIsAttached - if (isAttachedToWindow || composeViewContextIncrementedDuringInit) { - callback(composeViewContext) - } else { - onReadyForComposition = callback + fun setOnViewTreeOwnersAvailable(callback: (ViewTreeOwners) -> Unit) { + val viewTreeOwners = viewTreeOwners + if (viewTreeOwners != null) { + callback(viewTreeOwners) + } + if (!isAttachedToWindow) { + onViewTreeOwnersAvailable = callback } } @@ -2227,7 +2192,6 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV @OptIn(ExperimentalComposeUiApi::class) override fun onAttachedToWindow() { super.onAttachedToWindow() - isAttached = true if (SDK_INT < 30) { showLayoutBounds = getIsShowingLayoutBounds() } @@ -2235,13 +2199,12 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV insetsListener.onViewAttachedToWindow(this) } addNotificationForSysPropsChange(this) - if (!composeViewContextIncrementedDuringInit) { - composeViewContext.incrementViewCount() - snapshotObserver.startObserving() - } - composeViewContextIncrementedDuringInit = false + _windowInfo.isWindowFocused = hasWindowFocus() + _windowInfo.setOnInitializeContainerSize { calculateWindowSize(this) } + updateWindowMetrics() invalidateLayoutNodeMeasurement(root) invalidateLayers(root) + snapshotObserver.startObserving() ifDebug { if (autofillSupported()) { // TODO(b/333102566): Use _semanticAutofill after switching to the newer Autofill @@ -2250,17 +2213,57 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } } - retainedValuesStore = installLocalRetainedValuesStore() ?: ForgetfulRetainedValuesStore - - onReadyForComposition?.let { - it(composeViewContext) - onReadyForComposition = null + val lifecycleOwner = findViewTreeLifecycleOwner() + val savedStateRegistryOwner = findViewTreeSavedStateRegistryOwner() + val viewModelStoreOwner = findViewTreeViewModelStoreOwner() + + retainedValuesStore = + installLocalRetainedValuesStore(lifecycleOwner, viewModelStoreOwner) + ?: ForgetfulRetainedValuesStore + + val oldViewTreeOwners = viewTreeOwners + // We need to change the ViewTreeOwner if there isn't one yet (null) + // or if either the lifecycleOwner, savedStateRegistryOwner, viewModelStoreOwner has + // changed. + val resetViewTreeOwner = + oldViewTreeOwners == null || + ((lifecycleOwner != null && savedStateRegistryOwner != null) && + (lifecycleOwner !== oldViewTreeOwners.lifecycleOwner || + savedStateRegistryOwner !== oldViewTreeOwners.savedStateRegistryOwner || + viewModelStoreOwner !== oldViewTreeOwners.viewModelStoreOwner)) + if (resetViewTreeOwner) { + if (lifecycleOwner == null) { + throw IllegalStateException( + "Composed into the View which doesn't propagate ViewTreeLifecycleOwner!" + ) + } + if (savedStateRegistryOwner == null) { + throw IllegalStateException( + "Composed into the View which doesn't propagate" + + "ViewTreeSavedStateRegistryOwner!" + ) + } + oldViewTreeOwners?.lifecycleOwner?.lifecycle?.removeObserver(this) + lifecycleOwner.lifecycle.addObserver(this) + val viewTreeOwners = + ViewTreeOwners( + lifecycleOwner = lifecycleOwner, + savedStateRegistryOwner = savedStateRegistryOwner, + viewModelStoreOwner = viewModelStoreOwner, + ) + _viewTreeOwners = viewTreeOwners + onViewTreeOwnersAvailable?.invoke(viewTreeOwners) + onViewTreeOwnersAvailable = null } - val lifecycle = composeViewContext.lifecycleOwner.lifecycle + _inputModeManager.inputMode = if (isInTouchMode) Touch else Keyboard + + val lifecycle = + checkPreconditionNotNull(viewTreeOwners?.lifecycleOwner?.lifecycle) { + "No lifecycle owner exists" + } lifecycle.addObserver(this) lifecycle.addObserver(contentCaptureManager) - _inputModeManager.inputMode = if (isInTouchMode) Touch else Keyboard viewTreeObserver.addOnGlobalLayoutListener(this) viewTreeObserver.addOnScrollChangedListener(this) viewTreeObserver.addOnTouchModeChangeListener(this) @@ -2273,9 +2276,13 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV focusOwner.listeners += this } - private fun installLocalRetainedValuesStore(): RetainedValuesStore? { - val viewModelStoreOwner = composeViewContext.viewModelStoreOwner ?: return null - if (frameEndScheduler == null) return null + private fun installLocalRetainedValuesStore( + lifecycleOwner: LifecycleOwner?, + viewModelStoreOwner: ViewModelStoreOwner?, + ): RetainedValuesStore? { + val frameEndScheduler = frameEndScheduler + if (lifecycleOwner == null || viewModelStoreOwner == null || frameEndScheduler == null) + return null val retainedValuesStoreOwner = ViewModelProvider.create( @@ -2296,7 +2303,6 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV @OptIn(ExperimentalComposeUiApi::class) override fun onDetachedFromWindow() { super.onDetachedFromWindow() - isAttached = false if (ComposeUiFlags.areWindowInsetsRulersEnabled) { insetsListener.onViewDetachedFromWindow(this) } @@ -2306,11 +2312,14 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } removeNotificationForSysPropsChange(this) - composeViewContext.decrementViewCount() - val lifecycle = composeViewContext.lifecycleOwner.lifecycle + snapshotObserver.stopObserving() + _windowInfo.setOnInitializeContainerSize(null) + val lifecycle = + checkPreconditionNotNull(viewTreeOwners?.lifecycleOwner?.lifecycle) { + "No lifecycle owner exists" + } lifecycle.removeObserver(contentCaptureManager) lifecycle.removeObserver(this) - snapshotObserver.stopObserving() ifDebug { if (autofillSupported()) { // TODO(b/333102566): Use _semanticAutofill after switching to the newer Autofill @@ -2656,8 +2665,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV private fun sendMotionEvent(motionEvent: MotionEvent): ProcessResult { if (keyboardModifiersRequireUpdate) { keyboardModifiersRequireUpdate = false - composeViewContext.windowInfo.keyboardModifiers = - PointerKeyboardModifiers(motionEvent.metaState) + _windowInfo.keyboardModifiers = PointerKeyboardModifiers(motionEvent.metaState) } val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent, this) val action = motionEvent.actionMasked @@ -2843,6 +2851,10 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV viewToWindowMatrix.invertTo(windowToViewMatrix) } + private fun updateWindowMetrics() { + _windowInfo.updateContainerSizeIfObserved { calculateWindowSize(this) } + } + override fun onCheckIsTextEditor(): Boolean { val parentSession = textInputSessionMutex.currentSession @@ -2874,12 +2886,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - // While ComposeViewContext receives configuration change events through the - // context's ComponentCallback, some changes come through the view hierarchy and have - // to be notified that way. For example, a dev may trigger night mode on the Activity - // and it won't come through the context. We'll trigger the change here also - // to catch those types of changes. - composeViewContext.onConfigurationChanged(newConfig) + updateConfiguration(newConfig) } /** @@ -2897,7 +2904,36 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV */ private fun dispatchConfigurationChangeIfNeeded() { if (SDK_INT in 32..33) { - composeViewContext.onConfigurationChanged(resources.configuration) + updateConfiguration(resources.configuration) + } + } + + /** Updates this [AndroidComposeView] with properties from the updated [Configuration]. */ + private fun updateConfiguration(newConfig: Configuration) { + // Keep track of the old configuration for checking if components have updated + val oldConfig = configuration + if (oldConfig != newConfig) { + // Create a deep copy for comparison - both here in the future, and to allow + // consumers to properly use LocalConfiguration as a proper key. + configuration = Configuration(newConfig) + } else { + // Nothing changed at all, short circuit + return + } + // Density can only change if the densityDpi changed or if the fontScale changed. + // Otherwise, don't create a new Density object. + if ( + oldConfig.fontScale != newConfig.fontScale || + oldConfig.densityDpi != newConfig.densityDpi + ) { + density = Density(context) + } + if (oldConfig.diffForWindowMetricsChanged(newConfig)) { + updateWindowMetrics() + } + // Update the font family resolver if the font weight adjustment changed + if (oldConfig.fontWeightAdjustmentCompat != newConfig.fontWeightAdjustmentCompat) { + fontFamilyResolver = createFontFamilyResolver(context) } } @@ -3104,7 +3140,7 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } override val isLifecycleInResumedState: Boolean - get() = composeViewContext.lifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED + get() = viewTreeOwners?.lifecycleOwner?.lifecycle?.currentState == Lifecycle.State.RESUMED override fun shouldDelayChildPressedState(): Boolean = false @@ -3296,6 +3332,16 @@ internal class AndroidComposeView(context: Context, composeViewContext: ComposeV } } + /** Combines objects populated via ViewTree*Owner */ + class ViewTreeOwners( + /** The [LifecycleOwner] associated with this owner. */ + val lifecycleOwner: LifecycleOwner, + /** The [SavedStateRegistryOwner] associated with this owner. */ + val savedStateRegistryOwner: SavedStateRegistryOwner, + /** The [ViewModelStoreOwner] associated with this owner. */ + val viewModelStoreOwner: ViewModelStoreOwner?, + ) + private inner class RootModifierNode : Modifier.Node(), BringIntoViewModifierNode, @@ -3870,3 +3916,48 @@ internal class IndirectPointerNavigationGestureDetector( ignoreCurrentGestureStream = true } } + +/** + * A combined mask of all known configuration changes that do not affect the window metrics. + * + * For optimization purposes, any new configuration changes that don't result in the window metrics + * changes should be added to this list. + * + * Order is copied from `ActivityInfo.Config` with the config change types that can affect the + * window metrics excluded + */ +private const val maskForNonWindowMetricsChanges = + ActivityInfo.CONFIG_MCC or + ActivityInfo.CONFIG_MNC or + ActivityInfo.CONFIG_LOCALE or + ActivityInfo.CONFIG_TOUCHSCREEN or + ActivityInfo.CONFIG_KEYBOARD or + ActivityInfo.CONFIG_KEYBOARD_HIDDEN or + ActivityInfo.CONFIG_NAVIGATION or + ActivityInfo.CONFIG_UI_MODE or + ActivityInfo.CONFIG_LAYOUT_DIRECTION or + ActivityInfo.CONFIG_COLOR_MODE or + ActivityInfo.CONFIG_FONT_SCALE or + ActivityInfo.CONFIG_GRAMMATICAL_GENDER or + ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT // or + +// TODO(b/450557132): Add when compileSdk is bumped to 36 +// ActivityInfo.CONFIG_ASSETS_PATHS + +/** + * Diffs this [Configuration] with the [other] to determine if there were any configuration changes + * that could result in the window metrics being changed. + * + * We also can't just look at [Configuration.screenWidthDp] and [Configuration.screenHeightDp]: + * Those are represented by integer coordinates, which means that there is necessary rounding. + * Therefore, small window size changes that are 1 dp or less, may not change the rounded value of + * [Configuration.screenWidthDp] and [Configuration.screenHeightDp], but this does indeed cause a + * configuration change that is captured by a change in the non-public window configuration. + * + * Because the window configuration is not-public, and to guard against future configuration changes + * that may change the window metrics, the implementation for this is inverted: We assume that the + * window metrics may have changed if there were any changes in the configuration other than those + * defined by [maskForNonWindowMetricsChanges], which we know will not. + */ +private fun Configuration.diffForWindowMetricsChanged(other: Configuration): Boolean = + (diff(other) and maskForNonWindowMetricsChanges.inv()) != 0 diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt index d8c820188969d..401b95c881811 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt @@ -502,7 +502,7 @@ internal class AndroidComposeViewAccessibilityDelegateCompat(val view: AndroidCo private fun createNodeInfo(virtualViewId: Int): AccessibilityNodeInfoCompat? { if ( - view.composeViewContext.lifecycleOwner.lifecycle.currentState == + view.viewTreeOwners?.lifecycleOwner?.lifecycle?.currentState == Lifecycle.State.DESTROYED ) { return emptyNodeInfoOrNull() diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt index dacd80f2208e9..7885584d9c3da 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt @@ -16,13 +16,21 @@ package androidx.compose.ui.platform +import android.content.ComponentCallbacks2 import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalWithComputedDefaultOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.LocalSaveableStateRegistry import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.res.ImageVectorCache import androidx.compose.ui.res.ResourceIdCache import androidx.lifecycle.compose.LocalLifecycleOwner @@ -81,6 +89,118 @@ val LocalSavedStateRegistryOwner /** The CompositionLocal containing the current Compose [View]. */ val LocalView = staticCompositionLocalOf { noLocalProvidedFor("LocalView") } +@Composable +@OptIn(ExperimentalComposeUiApi::class) +internal fun ProvideAndroidCompositionLocals( + owner: AndroidComposeView, + content: @Composable () -> Unit, +) { + val view = owner + val context = view.context + val uriHandler = remember { AndroidUriHandler(context) } + val viewTreeOwners = + owner.viewTreeOwners + ?: throw IllegalStateException( + "Called when the ViewTreeOwnersAvailability is not yet in Available state" + ) + + val saveableStateRegistry = remember { + DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner) + } + DisposableEffect(Unit) { onDispose { saveableStateRegistry.dispose() } } + + val hapticFeedback = remember { + if (HapticDefaults.isPremiumVibratorEnabled(context)) { + DefaultHapticFeedback(owner.view) + } else { + NoHapticFeedback() + } + } + + val imageVectorCache = obtainImageVectorCache(context, owner.configuration) + val resourceIdCache = obtainResourceIdCache(context) + val scrollCaptureInProgress = + LocalScrollCaptureInProgress.current or owner.scrollCaptureInProgress + CompositionLocalProvider( + LocalConfiguration provides owner.configuration, + LocalContext provides context, + LocalLifecycleOwner provides viewTreeOwners.lifecycleOwner, + LocalSavedStateRegistryOwner provides viewTreeOwners.savedStateRegistryOwner, + LocalSaveableStateRegistry provides saveableStateRegistry, + LocalView provides owner.view, + LocalImageVectorCache provides imageVectorCache, + LocalResourceIdCache provides resourceIdCache, + LocalProvidableScrollCaptureInProgress provides scrollCaptureInProgress, + LocalHapticFeedback provides hapticFeedback, + ) { + ProvideCommonCompositionLocals(owner = owner, uriHandler = uriHandler, content = content) + } +} + +@Stable +@Composable +private fun obtainResourceIdCache(context: Context): ResourceIdCache { + val resourceIdCache = remember { ResourceIdCache() } + val callbacks = remember { + object : ComponentCallbacks2 { + override fun onConfigurationChanged(newConfig: Configuration) { + resourceIdCache.clear() + } + + @Deprecated("This callback is superseded by onTrimMemory") + @Suppress("OVERRIDE_DEPRECATION") // b/446706247 + override fun onLowMemory() { + resourceIdCache.clear() + } + + override fun onTrimMemory(level: Int) { + resourceIdCache.clear() + } + } + } + DisposableEffect(resourceIdCache) { + context.applicationContext.registerComponentCallbacks(callbacks) + onDispose { context.applicationContext.unregisterComponentCallbacks(callbacks) } + } + return resourceIdCache +} + +@Stable +@Composable +private fun obtainImageVectorCache( + context: Context, + configuration: Configuration?, +): ImageVectorCache { + val imageVectorCache = remember { ImageVectorCache() } + val currentConfiguration: Configuration = remember { + Configuration().apply { configuration?.let { this.setTo(it) } } + } + val callbacks = remember { + object : ComponentCallbacks2 { + override fun onConfigurationChanged(configuration: Configuration) { + val changedFlags = currentConfiguration.updateFrom(configuration) + imageVectorCache.prune(changedFlags) + currentConfiguration.setTo(configuration) + } + + @Deprecated("This callback is superseded by onTrimMemory") + @Suppress("OVERRIDE_DEPRECATION") // b/446706247 + override fun onLowMemory() { + imageVectorCache.clear() + } + + override fun onTrimMemory(level: Int) { + imageVectorCache.clear() + } + } + } + DisposableEffect(imageVectorCache) { + context.applicationContext.registerComponentCallbacks(callbacks) + onDispose { context.applicationContext.unregisterComponentCallbacks(callbacks) } + } + return imageVectorCache +} + private fun noLocalProvidedFor(name: String): Nothing { error("CompositionLocal $name not present") } diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt index 65795d7ae44b1..9ba63daf75603 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt @@ -26,22 +26,15 @@ import androidx.compose.runtime.Composition import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.Recomposer import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.ComposeUiFlags -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.InternalComposeUiApi import androidx.compose.ui.R import androidx.compose.ui.UiComposable import androidx.compose.ui.node.InternalCoreApi import androidx.compose.ui.node.Owner -import androidx.core.view.isEmpty -import androidx.core.view.isNotEmpty -import androidx.core.viewtree.getParentOrViewTreeDisjointParent import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.savedstate.SavedStateRegistryOwner -import androidx.savedstate.findViewTreeSavedStateRegistryOwner import java.lang.ref.WeakReference /** @@ -122,25 +115,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } } - /** - * [ComposeViewContext] used by this [ComposeView]. This can be set to allow a [ComposeView] to - * compose its content when not attached to the view hierarchy. Changing this to `null` will - * result in any existing composition being disposed. - */ - internal var composeViewContext: ComposeViewContext? = null - set(value) { - val existing = field - if (existing !== value) { - if (value == null) { - disposeComposition() - } else if (isNotEmpty()) { - val child = getChildAt(0) as? AndroidComposeView - child?.composeViewContext = value - } - field = value - } - } - /** * Set the [CompositionContext] that should be the parent of this view's composition. If * [parent] is `null` it will be determined automatically from the window the view is attached @@ -228,43 +202,13 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 * [CompositionContext] has been [set][setParentCompositionContext] explicitly. */ fun createComposition() { - check( - parentContext != null || - isAttachedToWindow || - (composeViewContext != null && composeViewContext?.view?.isAttachedToWindow == true) - ) { + check(parentContext != null || isAttachedToWindow) { "createComposition requires either a parent reference or the View to be attached" + "to a window. Attach the View or call setParentCompositionReference." } ensureCompositionCreated() } - /** - * Perform initial composition for this view, even if this view isn't attached to the hierarchy. - * If the view is never attached to the hierarchy, [disposeComposition] must be called for the - * composition to be cleaned up properly. If the view is attached to the hierarchy after - * [createComposition], detaching it will clean up the composition, so calling - * [disposeComposition] is unnecessary. - * - * If this method is called when the composition has already been created, it will update the - * [composeViewContext] being used for the composition. - * - * The [composeViewContext] values override any previously set [setParentCompositionContext] - * value. The [composeViewContext] values are used for all composition information, including - * [LifecycleOwner], [SavedStateRegistryOwner], and window information pulled from the - * [ComposeViewContext]'s attached View. - * - * @param composeViewContext The [ComposeViewContext] to use for the composition. The - * [ComposeViewContext.view] must be attached to the hierarchy. - */ - internal fun createComposition(composeViewContext: ComposeViewContext) { - check(composeViewContext.view.isAttachedToWindow) { - "createComposition requires the ComposeViewContext's view to be attached to a window." - } - this.composeViewContext = composeViewContext - ensureCompositionCreated() - } - private var creatingComposition = false private fun checkAddView() { @@ -313,97 +257,23 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive } ?: windowRecomposer.cacheIfAlive() - @OptIn(ExperimentalStdlibApi::class) @Suppress("DEPRECATION") // Still using ViewGroup.setContent for now private fun ensureCompositionCreated() { if (composition == null) { try { creatingComposition = true - val composeViewContext = composeViewContext - val effectiveComposeViewContext = - if (composeViewContext == null) { - val existingContext = - if (isEmpty()) null - else (getChildAt(0) as? AndroidComposeView)?.composeViewContext - val contextView = findViewTreeComposeViewRoot() - val foundComposeViewContext = contextView.composeViewContext - if (foundComposeViewContext == null) { - // Create one and store it for future create calls - val createdContext = - ComposeViewContext( - compositionContext = resolveParentCompositionContext(), - lifecycleOwner = - contextView.findViewTreeLifecycleOwner() - ?: existingContext?.lifecycleOwner - ?: throw IllegalStateException( - "Composed into the View which doesn't propagate ViewTreeLifecycleOwner!" - ), - savedStateRegistryOwner = - contextView.findViewTreeSavedStateRegistryOwner() - ?: existingContext?.savedStateRegistryOwner - ?: throw IllegalStateException( - "Composed into the View which doesn't propagate ViewTreeSavedStateRegistryOwner!" - ), - viewModelStoreOwner = - contextView.findViewTreeViewModelStoreOwner() - ?: existingContext?.viewModelStoreOwner, - view = contextView, - ) - contextView.composeViewContext = createdContext - createdContext - } else { - updateAutoCreatedComposeViewContext( - contextView, - foundComposeViewContext, - ) - } - } else { - composeViewContext - } - composition = setContent(effectiveComposeViewContext) { Content() } + composition = setContent(resolveParentCompositionContext()) { Content() } } finally { creatingComposition = false } } } - private fun updateAutoCreatedComposeViewContext( - contextView: View, - existingContext: ComposeViewContext, - ): ComposeViewContext { - val newContext = resolveParentCompositionContext() - val lifecycleOwner = contextView.findViewTreeLifecycleOwner() - val viewModelStoreOwner = contextView.findViewTreeViewModelStoreOwner() - val savedStateRegistryOwner = contextView.findViewTreeSavedStateRegistryOwner() - if ( - newContext === existingContext.compositionContext && - lifecycleOwner === existingContext.lifecycleOwner && - viewModelStoreOwner === existingContext.viewModelStoreOwner && - savedStateRegistryOwner === existingContext.savedStateRegistryOwner - ) { - // No changes - return existingContext - } - val createdContext = - ComposeViewContext( - compositionContext = newContext, - lifecycleOwner = lifecycleOwner ?: existingContext.lifecycleOwner, - savedStateRegistryOwner = - savedStateRegistryOwner ?: existingContext.savedStateRegistryOwner, - viewModelStoreOwner = viewModelStoreOwner, - view = contextView, - ) - contextView.composeViewContext = createdContext - return createdContext - } - /** * Dispose of the underlying composition and [requestLayout]. A new composition will be created * if [createComposition] is called or when needed to lay out this view. */ fun disposeComposition() { - val child = getChildAt(0) as? AndroidComposeView - child?.removeConnectionToComposeViewContext() composition?.dispose() composition = null requestLayout() @@ -420,17 +290,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 super.onAttachedToWindow() previousAttachedWindowToken = windowToken - if (composeViewContext == null) { - val child = if (isEmpty()) null else getChildAt(0) as? AndroidComposeView - if (child != null) { - val composeViewContext = child.composeViewContext - child.composeViewContext = - updateAutoCreatedComposeViewContext( - findViewTreeComposeViewRoot(), - composeViewContext, - ) - } - } if (shouldCreateCompositionOnAttachedToWindow) { ensureCompositionCreated() @@ -599,106 +458,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 fun setContent(content: @Composable () -> Unit) { shouldCreateCompositionOnAttachedToWindow = true this.content.value = content - if (isAttachedToWindow || composeViewContext != null) { + if (isAttachedToWindow) { createComposition() } } } - -@OptIn(ExperimentalComposeUiApi::class) -private fun View.findViewTreeComposeViewRoot(): View { - if (!isAttachedToWindow || !ComposeUiFlags.isSharedComposeViewContextEnabled) return this - - val lifecycleOwnerDepth = - findDepthToTag(androidx.lifecycle.runtime.R.id.view_tree_lifecycle_owner) - val savedStateRegistryOwnerDepth = - findDepthToTag(androidx.savedstate.R.id.view_tree_saved_state_registry_owner) - val maxDepth = minOf(lifecycleOwnerDepth, savedStateRegistryOwnerDepth) - - // Look for the View that has the lifecycle owner - var grandPreviousView: View = this - var previousView: View = this - var currentView: View? = this - var depth = 0 - while (currentView != null) { - if (depth == maxDepth) { - // Try not to return the DecorView because its context may not be set as we want - if (currentView.parent !is ViewGroup) { - return previousView - } - return currentView - } - val composeViewContext = currentView.composeViewContext - if (composeViewContext != null) { - return currentView - } - - depth++ - val parent = currentView.getParentOrViewTreeDisjointParent() as? View - grandPreviousView = previousView - previousView = currentView - currentView = parent - } - // Try not to return the DecorView because its context may not be set as we want - return grandPreviousView -} - -/** - * Finds the highest depth of View with the same value of [tag] set on it as the lowest View with - * [tag] set on it. This walks the up tree to the first View with [tag] and will continue to walk up - * the tree until a different [tag] value is set. Then the maximum depth of the View with the same - * [tag] value will be returned. A depth of 0 indicates that this [View] has a [tag] value and its - * ancestors don't have a value or have a different value. A value of [Int.MAX_VALUE] indicates that - * no ancestors have a value for [tag]. - */ -private fun View.findDepthToTag(tag: Int): Int { - var view: View? = this - var foundTag: Any? = null - var depth = 0 - var foundDepth = Int.MAX_VALUE - while (view != null) { - val tagValue = view.getTag(tag) - if (tagValue != null) { - if (foundTag == null) { - foundTag = tagValue - } else if (tagValue != foundTag) { - return foundDepth - } - foundDepth = depth - } - depth++ - view = view.getParentOrViewTreeDisjointParent() as? View - } - return foundDepth -} - -/** - * Returns the [ComposeViewContext] used in this View's part of the hierarchy, or `null` if one - * cannot be found or it doesn't match the values set for [View.findViewTreeLifecycleOwner] or - * [View.findViewTreeSavedStateRegistryOwner]. For example, if there is a [View.composeViewContext] - * set in the hierarchy, [findViewTreeComposeViewContext] on a child of that View will normally - * return that [ComposeViewContext]. However, if the child is within a Fragment, its - * [LifecycleOwner] differs from that set in the [View.composeViewContext], so - * [findViewTreeComposeViewContext] will return `null`. - * - * @see View.composeViewContext - */ -internal fun View.findViewTreeComposeViewContext(): ComposeViewContext? { - return findViewTreeComposeViewRoot().composeViewContext -} - -/** - * The [ComposeViewContext] that should be used for [AbstractComposeView]s in this part of the - * hierarchy, if they share the same [LifecycleOwner] and [SavedStateRegistryOwner]. - * - * @see View.findViewTreeComposeViewContext - */ -@Suppress("UNCHECKED_CAST") -internal var View.composeViewContext: ComposeViewContext? - get() = - (getTag(R.id.androidx_compose_ui_view_compose_view_context) - as? WeakReference) - ?.get() - set(value) { - setTag(R.id.androidx_compose_ui_view_compose_view_context, WeakReference(value)) - } diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeViewContext.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeViewContext.android.kt deleted file mode 100644 index 4d87fadd8f903..0000000000000 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeViewContext.android.kt +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright 2025 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.compose.ui.platform - -import android.annotation.SuppressLint -import android.content.ComponentCallbacks2 -import android.content.pm.ActivityInfo -import android.content.res.Configuration -import android.util.Log -import android.view.View -import android.view.ViewTreeObserver -import androidx.annotation.VisibleForTesting -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionContext -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.ProvidedValue -import androidx.compose.runtime.currentComposer -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.referentialEqualityPolicy -import androidx.compose.runtime.remember -import androidx.compose.runtime.retain.LocalRetainedValuesStore -import androidx.compose.runtime.retain.RetainedValuesStore -import androidx.compose.runtime.saveable.LocalSaveableStateRegistry -import androidx.compose.runtime.tooling.CompositionData -import androidx.compose.runtime.tooling.LocalInspectionTables -import androidx.compose.ui.R -import androidx.compose.ui.graphics.CanvasHolder -import androidx.compose.ui.hapticfeedback.HapticFeedback -import androidx.compose.ui.node.LayoutNodeDrawScope -import androidx.compose.ui.node.RootForTest -import androidx.compose.ui.res.ImageVectorCache -import androidx.compose.ui.res.ResourceIdCache -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.createFontFamilyResolver -import androidx.compose.ui.unit.Density -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModelStoreOwner -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.findViewTreeViewModelStoreOwner -import androidx.savedstate.SavedStateRegistryOwner -import androidx.savedstate.compose.LocalSavedStateRegistryOwner -import androidx.savedstate.findViewTreeSavedStateRegistryOwner - -/** - * [ComposeViewContext] can be used to compose a [ComposeView] while it isn't attached to the view - * hierarchy. This is useful when prefetching items for a RecyclerView, for example. To use it, call - * [AbstractComposeView.createComposition] with the [ComposeViewContext] after the content has been - * set. If the [ComposeView] is never attached to the hierarchy, - * [AbstractComposeView.disposeComposition] must be called to release resources and stop - * composition. If the [ComposeView] is attached to the hierarchy, it will stop composition once it - * has been removed from the hierarchy and calling [AbstractComposeView.disposeComposition] is - * unnecessary. It will start again if the [ComposeView.setContent] is called again, the View is - * reattached to the hierarchy, or [AbstractComposeView.createComposition] is called again. - * - * @param view A [View] attached to the same hierarchy as the [ComposeView]s constructed with this - * [ComposeViewContext]. This [View] must be attached before calling this constructor. - * @param compositionContext The [CompositionContext] used by [ComposeView]s constructed with this - * [ComposeViewContext]. The default value is obtained from [View.findViewTreeCompositionContext], - * or, if not found from the window [androidx.compose.runtime.Recomposer]. - * @param lifecycleOwner Used to govern the lifecycle-important aspects of [ComposeView]s - * constructed with this [ComposeViewContext]. The default value is obtained from - * [View.findViewTreeLifecycleOwner]. If not found, [IllegalStateException] will be thrown. - * @param savedStateRegistryOwner The [SavedStateRegistryOwner] used by [ComposeView]s constructed - * with this [ComposeViewContext]. The default value is obtained from - * [View.findViewTreeSavedStateRegistryOwner]. If not found, an [IllegalStateException] will be - * thrown. - * @param viewModelStoreOwner [ViewModelStoreOwner] to be used by [ComposeView]s to create - * [RetainedValuesStore]s. The default value is obtained from - * [View.findViewTreeViewModelStoreOwner]. - */ -internal class ComposeViewContext( - internal val view: View, - internal val compositionContext: CompositionContext = - view.findViewTreeCompositionContext() ?: view.windowRecomposer, - internal val lifecycleOwner: LifecycleOwner = - view.findViewTreeLifecycleOwner() - ?: throw IllegalStateException( - "Composed into a View which doesn't propagate ViewTreeLifecycleOwner!" - ), - internal val savedStateRegistryOwner: SavedStateRegistryOwner = - view.findViewTreeSavedStateRegistryOwner() - ?: throw IllegalStateException( - "Composed into a View which doesn't propagate ViewTreeSavedStateRegistryOwner!" - ), - internal val viewModelStoreOwner: ViewModelStoreOwner? = view.findViewTreeViewModelStoreOwner(), -) { - /** [ImageVectorCache] provided by [LocalImageVectorCache] */ - internal val imageVectorCache = ImageVectorCache() - - /** [ResourceIdCache] provided by [LocalResourceIdCache] */ - internal val resourceIdCache = ResourceIdCache() - - /** - * [Configuration] that was last received. Used to determine if there has been an update to the - * configuration or if we don't have to update the [configuration] instance. - */ - private val currentConfiguration: Configuration = - Configuration(view.context.resources.configuration) - - /** [Configuration] provided by [LocalConfiguration] */ - internal val configuration = mutableStateOf(Configuration(currentConfiguration)) - - /** [AccessibilityManager] provided by [LocalAccessibilityManager] */ - internal val accessibilityManager = AndroidAccessibilityManager(view.context) - - /** [UriHandler] provided by [LocalUriHandler] */ - internal val uriHandler = AndroidUriHandler(view.context) - - /** [ClipboardManager] provided by [LocalClipboardManager] */ - internal val clipboardManager: AndroidClipboardManager = AndroidClipboardManager(view.context) - - /** [Clipboard] provided by [LocalClipboard] */ - internal val clipboard: AndroidClipboard = AndroidClipboard(clipboardManager) - - /** [Font.ResourceLoader] provided by [LocalFontLoader] */ - @Suppress("DEPRECATION") - internal val fontLoader: Font.ResourceLoader = AndroidFontResourceLoader(view.context) - - /** - * [FontFamily.Resolver] provided by [LocalFontFamilyResolver]. This is updated when the - * configuration changes. - */ - internal val fontFamilyResolver = - mutableStateOf(createFontFamilyResolver(view.context), referentialEqualityPolicy()) - - /** [HapticFeedback] provided by [LocalHapticFeedback] */ - internal val hapticFeedback: HapticFeedback = - if (HapticDefaults.isPremiumVibratorEnabled(view.context)) { - DefaultHapticFeedback(view) - } else { - NoHapticFeedback() - } - - /** [ViewConfiguration] provided by [LocalViewConfiguration] */ - internal val viewConfiguration = - AndroidViewConfiguration(android.view.ViewConfiguration.get(view.context)) - - /** [LayoutNodeDrawScope] shared across all [ComposeView]s using this [ComposeViewContext] */ - internal val sharedDrawScope = LayoutNodeDrawScope() - - /** [Density] provided by [LocalDensity]. This is updated when the configuration changes. */ - internal val density = mutableStateOf(Density(view.context), referentialEqualityPolicy()) - - /** [WindowInfo] provide by [LocalWindowInfo]. */ - internal val windowInfo: LazyWindowInfo = LazyWindowInfo() - - /** - * A [CanvasHolder] that can be used for all AndroidComposeViews using this - * [ComposeViewContext]. - */ - internal val canvasHolder = CanvasHolder() - - /** Share the uncaughtExceptionHandler across all ComposeViews using this ComposeViewContext. */ - internal var uncaughtExceptionHandler: RootForTest.UncaughtExceptionHandler? = null - - /** - * The number of Views that are currently attached to the view hierarchy that are using this - * ComposeViewContext. - */ - @get:VisibleForTesting - internal var viewCount = 0 - private set - - /** Used for recalculating the window size whenever there is a change to the Window. */ - private val calculateWindowSizeLambda = { calculateWindowSize(view) } - - /** - * A single callback that handles observing configuration changes, memory calls, window focus - * changes, and [view] attach state changes. - */ - private val callback = - object : ComponentCallbacks2, ViewTreeObserver.OnWindowFocusChangeListener { - override fun onConfigurationChanged(configuration: Configuration) { - val changedFlags = currentConfiguration.updateFrom(configuration) - if (changedFlags != 0) { - imageVectorCache.prune(changedFlags) - this@ComposeViewContext.configuration.value = Configuration(configuration) - resourceIdCache.clear() - if (changedFlags and ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT != 0) { - fontFamilyResolver.value = createFontFamilyResolver(view.context) - } - if ( - changedFlags and - (ActivityInfo.CONFIG_DENSITY or ActivityInfo.CONFIG_FONT_SCALE) != 0 - ) { - density.value = Density(view.context) - } - if (changedFlags and MaskForNonWindowMetricsChanges.inv() != 0) { - windowInfo.updateContainerSizeIfObserved(calculateWindowSizeLambda) - } - } - } - - @Deprecated("This callback is superseded by onTrimMemory") - @Suppress("OVERRIDE_DEPRECATION") // b/446706247 - override fun onLowMemory() { - imageVectorCache.clear() - resourceIdCache.clear() - } - - override fun onTrimMemory(level: Int) { - imageVectorCache.clear() - resourceIdCache.clear() - } - - override fun onWindowFocusChanged(hasFocus: Boolean) { - windowInfo.isWindowFocused = hasFocus - } - } - - /** CompositionLocals that don't change between Views using this ComposeViewContext. */ - @Suppress("DEPRECATION") - private val compositionLocals = - arrayOf( - LocalLifecycleOwner provides lifecycleOwner, - LocalSavedStateRegistryOwner provides savedStateRegistryOwner, - LocalImageVectorCache provides imageVectorCache, - LocalResourceIdCache provides resourceIdCache, - LocalHapticFeedback provides hapticFeedback, - LocalAccessibilityManager provides accessibilityManager, - LocalFontLoader providesDefault fontLoader, - LocalUriHandler provides uriHandler, - LocalViewConfiguration provides viewConfiguration, - LocalWindowInfo provides windowInfo, - LocalClipboardManager provides clipboardManager, - LocalClipboard provides clipboard, - ) - - /** - * Called when an AndroidComposeView is attached to the window. This will start observation if - * it is the first view using this ComposeViewContext and it is created by the ComposeView. - * - * @see viewCount - */ - internal fun incrementViewCount() { - viewCount++ - if (viewCount == 1) { - startObserving() - } - } - - /** - * Called when the AndroidComposeView is detached from the window. If this ComposeViewContext - * was created by ComposeView and it is called by the last AndroidComposeView, observation will - * be stopped. - * - * @see viewCount - */ - internal fun decrementViewCount() { - viewCount-- - if (viewCount < 0) { - Log.e("ComposeViewContext", "View count has dropped below 0") - viewCount = 0 - } - if (viewCount == 0) { - stopObserving() - } - } - - /** Start observing configuration changes and window changes. */ - private fun startObserving() { - view.context.registerComponentCallbacks(callback) - // Tests that have the application rotate don't cause the view's context to trigger - // onConfigurationChanged, so we must register in the application context, too. - view.context.applicationContext.registerComponentCallbacks(callback) - onConfigurationChanged(view.resources.configuration) - windowInfo.isWindowFocused = view.hasWindowFocus() - windowInfo.setOnInitializeContainerSize(calculateWindowSizeLambda) - windowInfo.updateContainerSizeIfObserved(calculateWindowSizeLambda) - view.viewTreeObserver.addOnWindowFocusChangeListener(callback) - } - - /** Stop observing configuration changes and window changes. */ - private fun stopObserving() { - view.context.unregisterComponentCallbacks(callback) - view.context.applicationContext.unregisterComponentCallbacks(callback) - windowInfo.setOnInitializeContainerSize(null) - view.viewTreeObserver.removeOnWindowFocusChangeListener(callback) - } - - /** - * Called by [AndroidComposeView]s when it detects configuration changes. Not all configuration - * changes come through the Activity, so it is important to update through attached - * AndroidComposeViews also. - */ - internal fun onConfigurationChanged(configuration: Configuration) { - callback.onConfigurationChanged(configuration) - } - - /** Provide common CompositionLocals. */ - @Suppress("DEPRECATION") - @Composable - internal fun ProvideCompositionLocals( - owner: AndroidComposeView, - content: @Composable () -> Unit, - ) { - @Suppress("UNCHECKED_CAST") - val inspectionTable = - owner.getTag(R.id.inspection_slot_table_set) as? MutableSet - ?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set) - as? MutableSet - if (inspectionTable != null) { - inspectionTable.add(currentComposer.compositionData) - currentComposer.collectParameterInformation() - } - val saveableStateRegistry = remember { - DisposableSaveableStateRegistry(owner, savedStateRegistryOwner) - } - DisposableEffect(Unit) { onDispose { saveableStateRegistry.dispose() } } - - val scrollCaptureInProgress = - LocalScrollCaptureInProgress.current or owner.scrollCaptureInProgress - val locals = compositionLocals.copyOf(compositionLocals.size + 22) - var index = compositionLocals.size - locals[index++] = LocalContext provides owner.context - locals[index++] = LocalInspectionTables provides inspectionTable - locals[index++] = LocalDensity provides owner.density - locals[index++] = LocalConfiguration provides configuration.value - locals[index++] = LocalSaveableStateRegistry provides saveableStateRegistry - locals[index++] = LocalView provides owner.view - locals[index++] = LocalProvidableScrollCaptureInProgress provides scrollCaptureInProgress - locals[index++] = LocalAutofill provides owner.autofill - locals[index++] = LocalAutofillManager provides owner.autofillManager - locals[index++] = LocalAutofillTree provides owner.autofillTree - locals[index++] = LocalFocusManager provides owner.focusOwner - locals[index++] = LocalFontFamilyResolver providesDefault owner.fontFamilyResolver - locals[index++] = LocalLayoutDirection provides owner.layoutDirection - locals[index++] = LocalRetainedValuesStore provides owner.retainedValuesStore - locals[index++] = LocalInputModeManager provides owner.inputModeManager - locals[index++] = LocalTextInputService provides owner.textInputService - locals[index++] = LocalSoftwareKeyboardController provides owner.softwareKeyboardController - locals[index++] = LocalTextToolbar provides owner.textToolbar - locals[index++] = LocalViewConfiguration provides owner.viewConfiguration - locals[index++] = LocalPointerIconService provides owner.pointerIconService - locals[index++] = LocalGraphicsContext provides owner.graphicsContext - @SuppressLint("VisibleForTests") - locals[index++] = LocalProvidableLocaleList provides owner.localeList - @Suppress("UNCHECKED_CAST") - CompositionLocalProvider(values = locals as Array>, content = content) - } -} - -/** - * A combined mask of all known configuration changes that do not affect the window metrics. - * - * For optimization purposes, any new configuration changes that don't result in the window metrics - * changes should be added to this list. - * - * Order is copied from `ActivityInfo.Config` with the config change types that can affect the - * window metrics excluded - */ -private const val MaskForNonWindowMetricsChanges = - ActivityInfo.CONFIG_MCC or - ActivityInfo.CONFIG_MNC or - ActivityInfo.CONFIG_LOCALE or - ActivityInfo.CONFIG_TOUCHSCREEN or - ActivityInfo.CONFIG_KEYBOARD or - ActivityInfo.CONFIG_KEYBOARD_HIDDEN or - ActivityInfo.CONFIG_NAVIGATION or - ActivityInfo.CONFIG_UI_MODE or - ActivityInfo.CONFIG_LAYOUT_DIRECTION or - ActivityInfo.CONFIG_FONT_SCALE or - ActivityInfo.CONFIG_COLOR_MODE or - ActivityInfo.CONFIG_GRAMMATICAL_GENDER or - ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT - -// TODO(b/450557132): Add when compileSdk is bumped to 36 -// ActivityInfo.CONFIG_ASSETS_PATHS diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt index 244320f1c6aa7..376cad329f2cb 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt @@ -15,16 +15,20 @@ */ package androidx.compose.ui.platform -import android.os.Looper import android.view.View import android.view.ViewGroup import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition +import androidx.compose.runtime.CompositionContext +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionServiceKey import androidx.compose.runtime.CompositionServices import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Recomposer +import androidx.compose.runtime.currentComposer import androidx.compose.runtime.tooling.CompositionData +import androidx.compose.runtime.tooling.LocalInspectionTables import androidx.compose.ui.R import androidx.compose.ui.node.LayoutNode import androidx.compose.ui.node.UiApplier @@ -48,50 +52,56 @@ internal actual fun createApplier(container: LayoutNode): AbstractApplier Unit, ): Composition { GlobalSnapshotManager.ensureStarted() val composeView = if (childCount > 0) { - (getChildAt(0) as? AndroidComposeView)?.also { - it.composeViewContext = composeViewContext - } + getChildAt(0) as? AndroidComposeView } else { removeAllViews() null } - ?: AndroidComposeView(context, composeViewContext).also { + ?: AndroidComposeView(context, parent.effectCoroutineContext).also { addView(it.view, DefaultLayoutParams) } - composeView.composeViewContext = composeViewContext - if (this.composeViewContext != null) { - composeViewContext.incrementViewCount() - composeView.snapshotObserver.startObserving() - composeView.composeViewContextIncrementedDuringInit = true - } + return doSetContent(composeView, parent, content) +} - if (isDebugInspectorInfoEnabled && composeView.getTag(R.id.inspection_slot_table_set) == null) { - composeView.setTag( +private fun doSetContent( + owner: AndroidComposeView, + parent: CompositionContext, + content: @Composable () -> Unit, +): Composition { + if (isDebugInspectorInfoEnabled && owner.getTag(R.id.inspection_slot_table_set) == null) { + owner.setTag( R.id.inspection_slot_table_set, Collections.newSetFromMap(WeakHashMap()), ) } + val wrapped = - composeView.getTag(R.id.wrapped_composition_tag) as? WrappedComposition - ?: WrappedComposition( - composeView, - Composition(UiApplier(composeView.root), composeViewContext.compositionContext), - ) - .also { composeView.setTag(R.id.wrapped_composition_tag, it) } + owner.view.getTag(R.id.wrapped_composition_tag) as? WrappedComposition + ?: WrappedComposition(owner, Composition(UiApplier(owner.root), parent)).also { + owner.view.setTag(R.id.wrapped_composition_tag, it) + } wrapped.setContent(content) - composeView.frameEndScheduler = - FrameEndScheduler(composeViewContext.compositionContext::scheduleFrameEndCallback) + // When the CoroutineContext between the owner and parent doesn't match, we need to reset it + // to this new parent's CoroutineContext, because the previous CoroutineContext was cancelled. + // This usually happens when the owner (AndroidComposeView) wasn't completely torn down during a + // config change. That expected scenario occurs when the manifest's configChanges includes + // 'screenLayout' and the user selects a pop-up view for the app. + if (owner.coroutineContext != parent.effectCoroutineContext) { + owner.coroutineContext = parent.effectCoroutineContext + } + + owner.frameEndScheduler = FrameEndScheduler(parent::scheduleFrameEndCallback) return wrapped } @@ -103,30 +113,34 @@ private class WrappedComposition(val owner: AndroidComposeView, val original: Co private var lastContent: @Composable () -> Unit = {} override fun setContent(content: @Composable () -> Unit) { - owner.setOnReadyForComposition { composeViewContext -> + owner.setOnViewTreeOwnersAvailable { if (!disposed) { - val lifecycle = composeViewContext.lifecycleOwner.lifecycle + val lifecycle = it.lifecycleOwner.lifecycle lastContent = content if (addedToLifecycle == null) { + addedToLifecycle = lifecycle // this will call ON_CREATE synchronously if we already created - if (Looper.myLooper() != composeViewContext.view.handler.looper) { - composeViewContext.view.post { - if (!disposed) { - addedToLifecycle = lifecycle - lifecycle.addObserver(this) - } - } - } else { - addedToLifecycle = lifecycle - lifecycle.addObserver(this) - } + lifecycle.addObserver(this) } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) { original.setContent { + @Suppress("UNCHECKED_CAST") + val inspectionTable = + owner.getTag(R.id.inspection_slot_table_set) + as? MutableSet + ?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set) + as? MutableSet + if (inspectionTable != null) { + inspectionTable.add(currentComposer.compositionData) + currentComposer.collectParameterInformation() + } + // TODO(mnuzen): Combine the two boundsUpdatesLoop() into one LaunchedEffect LaunchedEffect(owner) { owner.boundsUpdatesAccessibilityEventLoop() } LaunchedEffect(owner) { owner.boundsUpdatesContentCaptureEventLoop() } - composeViewContext.ProvideCompositionLocals(owner, content) + CompositionLocalProvider(LocalInspectionTables provides inspectionTable) { + ProvideAndroidCompositionLocals(owner, content) + } } } } @@ -138,7 +152,6 @@ private class WrappedComposition(val owner: AndroidComposeView, val original: Co disposed = true owner.view.setTag(R.id.wrapped_composition_tag, null) addedToLifecycle?.removeObserver(this) - addedToLifecycle = null } original.dispose() } diff --git a/compose/ui/ui/src/androidMain/res/values/ids.xml b/compose/ui/ui/src/androidMain/res/values/ids.xml index f2423782eccb5..5c2acf9c0baa5 100644 --- a/compose/ui/ui/src/androidMain/res/values/ids.xml +++ b/compose/ui/ui/src/androidMain/res/values/ids.xml @@ -55,5 +55,4 @@ - diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposeUiFlags.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposeUiFlags.kt index 8a946020d8639..a232d01c95c62 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposeUiFlags.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposeUiFlags.kt @@ -172,13 +172,4 @@ object ComposeUiFlags { @field:Suppress("MutableBareField") @JvmField var isTraversableDelegatesFixEnabled: Boolean = true - - /** - * This flag enables ComposeViewContext to be created automatically and used across ComposeViews - * within the same hierarchy. With the flag disabled, ComposeViewContext will only be created - * when explicitly provided to a ComposeView. - */ - @field:Suppress("MutableBareField") - @JvmField - var isSharedComposeViewContextEnabled: Boolean = true } diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt index 99028eb953dd6..8b285462758d0 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt @@ -899,6 +899,7 @@ internal class LayoutNode( } coordinator = coordinator?.wrappedBy } + innerLayerCoordinatorIsDirty = false } val layerCoordinator = _innerLayerCoordinator if (layerCoordinator != null) { diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt index 0dd0bbe0ffcbd..6c756f5e290a6 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt @@ -20,11 +20,15 @@ package androidx.compose.ui.platform import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting +import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocal +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalWithComputedDefaultOf +import androidx.compose.runtime.retain.LocalRetainedValuesStore import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.autofill.Autofill import androidx.compose.ui.autofill.AutofillManager import androidx.compose.ui.autofill.AutofillTree @@ -223,6 +227,42 @@ val LocalScrollCaptureInProgress: CompositionLocal */ val LocalCursorBlinkEnabled: ProvidableCompositionLocal = staticCompositionLocalOf { true } +@ExperimentalComposeUiApi +@Composable +internal fun ProvideCommonCompositionLocals( + owner: Owner, + uriHandler: UriHandler, + content: @Composable () -> Unit, +) { + CompositionLocalProvider( + LocalAccessibilityManager provides owner.accessibilityManager, + LocalAutofill provides owner.autofill, + LocalAutofillManager provides owner.autofillManager, + LocalAutofillTree provides owner.autofillTree, + LocalClipboardManager provides owner.clipboardManager, + LocalClipboard provides owner.clipboard, + LocalDensity provides owner.density, + LocalFocusManager provides owner.focusOwner, + @Suppress("DEPRECATION") LocalFontLoader providesDefault + @Suppress("DEPRECATION") owner.fontLoader, + LocalFontFamilyResolver providesDefault owner.fontFamilyResolver, + LocalHapticFeedback provides owner.hapticFeedBack, + LocalInputModeManager provides owner.inputModeManager, + LocalLayoutDirection provides owner.layoutDirection, + LocalTextInputService provides owner.textInputService, + LocalSoftwareKeyboardController provides owner.softwareKeyboardController, + LocalTextToolbar provides owner.textToolbar, + LocalUriHandler provides uriHandler, + LocalViewConfiguration provides owner.viewConfiguration, + LocalWindowInfo provides owner.windowInfo, + LocalPointerIconService provides owner.pointerIconService, + LocalGraphicsContext provides owner.graphicsContext, + LocalRetainedValuesStore provides owner.retainedValuesStore, + LocalProvidableLocaleList provides owner.localeList, + content = content, + ) +} + private fun noLocalProvidedFor(name: String): Nothing { error("CompositionLocal $name not present") } diff --git a/core/core-pip/src/main/java/androidx/core/pip/ViewBoundsTracker.kt b/core/core-pip/src/main/java/androidx/core/pip/ViewBoundsTracker.kt new file mode 100644 index 0000000000000..de54d3b887d7b --- /dev/null +++ b/core/core-pip/src/main/java/androidx/core/pip/ViewBoundsTracker.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2026 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.core.pip + +import android.graphics.Rect +import android.view.View +import android.view.ViewTreeObserver +import androidx.annotation.UiThread + +/** + * Tracks the global visible bounds of a [View]. + * + * This class monitors layout changes and scrolls affecting the specified [view] and notifies + * registered listeners when its visible bounds on the screen change. The bounds are obtained using + * [View.getGlobalVisibleRect]. + * + * Listening starts automatically when the view is attached to a window and stops when it's + * detached. + */ +@UiThread +internal class ViewBoundsTracker(private val view: View) { + + /** Interface for listening to changes in the view's global visible bounds. */ + interface OnViewBoundsChangedListener { + /** + * Called when the view's global visible bounds have changed. + * + * @param view The view whose bounds changed. + * @param newBounds The new global visible bounds of the view. This will be an empty Rect if + * the view is not visible. + */ + fun onViewBoundsChanged(view: View, newBounds: Rect) + } + + private val listeners = mutableSetOf() + private val currentBounds = Rect() + private var isTracking = false + + // Listener for changes to the view's own layout + private val layoutChangeListener = + View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> updateBounds() } + + // Listener for scroll changes within the view tree + private val scrollChangedListener = ViewTreeObserver.OnScrollChangedListener { updateBounds() } + + // Listener for global layout changes in the view tree + private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener { updateBounds() } + + // Listener to start/stop tracking based on window attachment + private val attachStateChangeListener = + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + startTracking() + } + + override fun onViewDetachedFromWindow(v: View) { + stopTracking() + } + } + + init { + // If the view is already attached when the tracker is created, start tracking. + if (view.isAttachedToWindow) { + startTracking() + } + // Always add the OnAttachStateChangeListener to handle future attachments/detachments. + view.addOnAttachStateChangeListener(attachStateChangeListener) + } + + /** Adds a listener to be notified of bounds changes. */ + fun addListener(listener: OnViewBoundsChangedListener) { + listeners.add(listener) + } + + /** Removes a previously added listener. */ + fun removeListener(listener: OnViewBoundsChangedListener) { + listeners.remove(listener) + } + + /** + * Releases resources and stops tracking. Call this when the tracker is no longer needed to + * prevent potential memory leaks, especially if the tracker instance lives longer than the + * view. + */ + fun release() { + stopTracking() + view.removeOnAttachStateChangeListener(attachStateChangeListener) + listeners.clear() + } + + private fun startTracking() { + if (isTracking) return + isTracking = true + + view.addOnLayoutChangeListener(layoutChangeListener) + val vto = view.viewTreeObserver + if (vto.isAlive) { + vto.addOnScrollChangedListener(scrollChangedListener) + vto.addOnGlobalLayoutListener(globalLayoutListener) + } + // Perform an initial bounds check. + updateBounds() + } + + private fun stopTracking() { + if (!isTracking) return + isTracking = false + + view.removeOnLayoutChangeListener(layoutChangeListener) + val vto = view.viewTreeObserver + if (vto.isAlive) { + vto.removeOnScrollChangedListener(scrollChangedListener) + vto.removeOnGlobalLayoutListener(globalLayoutListener) + } + // Reset currentBounds when not tracking + currentBounds.setEmpty() + } + + private fun updateBounds() { + if (!view.isAttachedToWindow) return + + val newBounds = Rect() + // getGlobalVisibleRect returns true if the view is at least partially visible. + val isVisible = view.getGlobalVisibleRect(newBounds) + + // Use an empty Rect if the view is not visible at all. + val effectiveBounds = if (isVisible) newBounds else Rect() + + if (effectiveBounds != currentBounds) { + currentBounds.set(effectiveBounds) + // Create a copy of the bounds to pass to listeners. + val boundsCopy = Rect(currentBounds) + // Iterate over a copy of the listeners set to avoid issues if a listener + // modifies the set during iteration. + listeners.toList().forEach { listener -> + listener.onViewBoundsChanged(view, boundsCopy) + } + } + } +} diff --git a/datastore/datastore-guava/src/main/java/androidx/datastore/guava/GuavaDataStore.kt b/datastore/datastore-guava/src/main/java/androidx/datastore/guava/GuavaDataStore.kt index a639701822fdc..ad352471f0f6f 100644 --- a/datastore/datastore-guava/src/main/java/androidx/datastore/guava/GuavaDataStore.kt +++ b/datastore/datastore-guava/src/main/java/androidx/datastore/guava/GuavaDataStore.kt @@ -37,6 +37,7 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.asCoroutineDispatcher /** @@ -191,7 +192,7 @@ internal constructor( serializer = serializer, corruptionHandler = corruptionHandler, migrations = dataMigrations, - scope = CoroutineScope(coroutineDispatcher), + scope = CoroutineScope(coroutineDispatcher + SupervisorJob()), ) } else { DataStoreFactory.create( @@ -199,7 +200,7 @@ internal constructor( serializer = serializer, corruptionHandler = corruptionHandler, migrations = dataMigrations, - scope = CoroutineScope(coroutineDispatcher), + scope = CoroutineScope(coroutineDispatcher + SupervisorJob()), ) } as? CurrentDataProviderStore diff --git a/docs-public/build.gradle b/docs-public/build.gradle index f03c0685bfac0..21f2cc4ca3447 100644 --- a/docs-public/build.gradle +++ b/docs-public/build.gradle @@ -15,9 +15,9 @@ android { } dependencies { - docs("androidx.activity:activity:1.12.2") - docs("androidx.activity:activity-compose:1.12.2") - docs("androidx.activity:activity-ktx:1.12.2") + docs("androidx.activity:activity:1.13.0-alpha01") + docs("androidx.activity:activity-compose:1.13.0-alpha01") + docs("androidx.activity:activity-ktx:1.13.0-alpha01") kmpDocs("androidx.annotation:annotation:1.9.1") docs("androidx.annotation:annotation-experimental:1.6.0-alpha01") docs("androidx.appcompat:appcompat:1.7.1") @@ -66,55 +66,55 @@ dependencies { docs("androidx.car.app:app-projected:1.8.0-alpha03") docs("androidx.car.app:app-testing:1.8.0-alpha03") docs("androidx.cardview:cardview:1.0.0") - kmpDocs("androidx.collection:collection:1.6.0-alpha01") - docs("androidx.collection:collection-ktx:1.6.0-alpha01") - kmpDocs("androidx.compose.animation:animation:1.11.0-alpha02") - kmpDocs("androidx.compose.animation:animation-core:1.11.0-alpha02") - kmpDocs("androidx.compose.animation:animation-graphics:1.11.0-alpha02") - kmpDocs("androidx.compose.foundation:foundation:1.11.0-alpha02") - kmpDocs("androidx.compose.foundation:foundation-layout:1.11.0-alpha02") - kmpDocs("androidx.compose.material3.adaptive:adaptive:1.3.0-alpha05") - kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.3.0-alpha05") - kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation:1.3.0-alpha05") - kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation3:1.3.0-alpha05") - kmpDocs("androidx.compose.material3:material3:1.5.0-alpha11") - kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha11") - kmpDocs("androidx.compose.material3:material3-window-size-class:1.5.0-alpha11") - kmpDocs("androidx.compose.material:material:1.11.0-alpha02") + kmpDocs("androidx.collection:collection:1.6.0-beta01") + docs("androidx.collection:collection-ktx:1.6.0-beta01") + kmpDocs("androidx.compose.animation:animation:1.11.0-alpha03") + kmpDocs("androidx.compose.animation:animation-core:1.11.0-alpha03") + kmpDocs("androidx.compose.animation:animation-graphics:1.11.0-alpha03") + kmpDocs("androidx.compose.foundation:foundation:1.11.0-alpha03") + kmpDocs("androidx.compose.foundation:foundation-layout:1.11.0-alpha03") + kmpDocs("androidx.compose.material3.adaptive:adaptive:1.3.0-alpha06") + kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.3.0-alpha06") + kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation:1.3.0-alpha06") + kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation3:1.3.0-alpha06") + kmpDocs("androidx.compose.material3:material3:1.5.0-alpha12") + kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha12") + kmpDocs("androidx.compose.material3:material3-window-size-class:1.5.0-alpha12") + kmpDocs("androidx.compose.material:material:1.11.0-alpha03") kmpDocs("androidx.compose.material:material-icons-core:1.7.8") - docs("androidx.compose.material:material-navigation:1.11.0-alpha02") - kmpDocs("androidx.compose.material:material-ripple:1.11.0-alpha02") - docs("androidx.compose.remote:remote-core:1.0.0-alpha01") - kmpDocs("androidx.compose.remote:remote-creation:1.0.0-alpha01") - docs("androidx.compose.remote:remote-creation-compose:1.0.0-alpha01") - docs("androidx.compose.remote:remote-creation-core:1.0.0-alpha01") - docs("androidx.compose.remote:remote-player-compose:1.0.0-alpha01") - docs("androidx.compose.remote:remote-player-core:1.0.0-alpha01") - docs("androidx.compose.remote:remote-player-view:1.0.0-alpha01") - docs("androidx.compose.remote:remote-tooling-preview:1.0.0-alpha01") - kmpDocs("androidx.compose.runtime:runtime:1.11.0-alpha02") - kmpDocs("androidx.compose.runtime:runtime-annotation:1.11.0-alpha02") - docs("androidx.compose.runtime:runtime-livedata:1.11.0-alpha02") - kmpDocs("androidx.compose.runtime:runtime-retain:1.11.0-alpha02") - kmpDocs("androidx.compose.runtime:runtime-rxjava2:1.11.0-alpha02") - kmpDocs("androidx.compose.runtime:runtime-rxjava3:1.11.0-alpha02") - kmpDocs("androidx.compose.runtime:runtime-saveable:1.11.0-alpha02") - docs("androidx.compose.runtime:runtime-tracing:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-geometry:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-graphics:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-test:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-test-accessibility:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-test-junit4:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-test-junit4-accessibility:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-text:1.11.0-alpha02") - docs("androidx.compose.ui:ui-text-google-fonts:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-tooling:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-tooling-data:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-tooling-preview:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-unit:1.11.0-alpha02") - kmpDocs("androidx.compose.ui:ui-util:1.11.0-alpha02") - docs("androidx.compose.ui:ui-viewbinding:1.11.0-alpha02") + docs("androidx.compose.material:material-navigation:1.11.0-alpha03") + kmpDocs("androidx.compose.material:material-ripple:1.11.0-alpha03") + docs("androidx.compose.remote:remote-core:1.0.0-alpha02") + kmpDocs("androidx.compose.remote:remote-creation:1.0.0-alpha02") + docs("androidx.compose.remote:remote-creation-compose:1.0.0-alpha02") + docs("androidx.compose.remote:remote-creation-core:1.0.0-alpha02") + docs("androidx.compose.remote:remote-player-compose:1.0.0-alpha02") + docs("androidx.compose.remote:remote-player-core:1.0.0-alpha02") + docs("androidx.compose.remote:remote-player-view:1.0.0-alpha02") + docs("androidx.compose.remote:remote-tooling-preview:1.0.0-alpha02") + kmpDocs("androidx.compose.runtime:runtime:1.11.0-alpha03") + kmpDocs("androidx.compose.runtime:runtime-annotation:1.11.0-alpha03") + docs("androidx.compose.runtime:runtime-livedata:1.11.0-alpha03") + kmpDocs("androidx.compose.runtime:runtime-retain:1.11.0-alpha03") + kmpDocs("androidx.compose.runtime:runtime-rxjava2:1.11.0-alpha03") + kmpDocs("androidx.compose.runtime:runtime-rxjava3:1.11.0-alpha03") + kmpDocs("androidx.compose.runtime:runtime-saveable:1.11.0-alpha03") + docs("androidx.compose.runtime:runtime-tracing:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-geometry:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-graphics:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-test:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-test-accessibility:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-test-junit4:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-test-junit4-accessibility:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-text:1.11.0-alpha03") + docs("androidx.compose.ui:ui-text-google-fonts:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-tooling:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-tooling-data:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-tooling-preview:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-unit:1.11.0-alpha03") + kmpDocs("androidx.compose.ui:ui-util:1.11.0-alpha03") + docs("androidx.compose.ui:ui-viewbinding:1.11.0-alpha03") docs("androidx.concurrent:concurrent-futures:1.3.0") docs("androidx.concurrent:concurrent-futures-ktx:1.3.0") docs("androidx.constraintlayout:constraintlayout:2.2.1") @@ -122,22 +122,23 @@ dependencies { docs("androidx.constraintlayout:constraintlayout-core:1.1.1") docs("androidx.contentpager:contentpager:1.0.0") docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0") - docs("androidx.core:core:1.17.0") + docs("androidx.core:core:1.18.0-alpha01") docs("androidx.core:core-animation:1.0.0") docs("androidx.core:core-animation-testing:1.0.0") docs("androidx.core:core-backported-fixes:1.0.0") docs("androidx.core:core-google-shortcuts:1.2.0-alpha01") docs("androidx.core:core-i18n:1.0.0") - docs("androidx.core:core-ktx:1.17.0") + docs("androidx.core:core-ktx:1.18.0-alpha01") docs("androidx.core:core-location-altitude:1.0.0-beta01") docs("androidx.core:core-performance:1.0.0") docs("androidx.core:core-performance-play-services:1.0.0") docs("androidx.core:core-performance-testing:1.0.0") + docs("androidx.core:core-pip:1.0.0-alpha01") docs("androidx.core:core-remoteviews:1.1.0") docs("androidx.core:core-role:1.2.0-alpha01") docs("androidx.core:core-splashscreen:1.2.0") docs("androidx.core:core-telecom:1.1.0-alpha02") - docs("androidx.core:core-testing:1.17.0") + docs("androidx.core:core-testing:1.18.0-alpha01") docs("androidx.core.uwb:uwb:1.0.0-alpha11") docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha11") docs("androidx.core:core-viewtree:1.0.0") @@ -154,16 +155,16 @@ dependencies { docs("androidx.cursoradapter:cursoradapter:1.0.0") docs("androidx.customview:customview:1.2.0") docs("androidx.customview:customview-poolingcontainer:1.1.0") - kmpDocs("androidx.datastore:datastore:1.3.0-alpha03") - kmpDocs("androidx.datastore:datastore-core:1.3.0-alpha03") - kmpDocs("androidx.datastore:datastore-core-okio:1.3.0-alpha03") - docs("androidx.datastore:datastore-guava:1.3.0-alpha03") - kmpDocs("androidx.datastore:datastore-preferences:1.3.0-alpha03") - kmpDocs("androidx.datastore:datastore-preferences-core:1.3.0-alpha03") - docs("androidx.datastore:datastore-preferences-rxjava2:1.3.0-alpha03") - docs("androidx.datastore:datastore-preferences-rxjava3:1.3.0-alpha03") - docs("androidx.datastore:datastore-rxjava2:1.3.0-alpha03") - docs("androidx.datastore:datastore-rxjava3:1.3.0-alpha03") + kmpDocs("androidx.datastore:datastore:1.3.0-alpha04") + kmpDocs("androidx.datastore:datastore-core:1.3.0-alpha04") + kmpDocs("androidx.datastore:datastore-core-okio:1.3.0-alpha04") + docs("androidx.datastore:datastore-guava:1.3.0-alpha04") + kmpDocs("androidx.datastore:datastore-preferences:1.3.0-alpha04") + kmpDocs("androidx.datastore:datastore-preferences-core:1.3.0-alpha04") + docs("androidx.datastore:datastore-preferences-rxjava2:1.3.0-alpha04") + docs("androidx.datastore:datastore-preferences-rxjava3:1.3.0-alpha04") + docs("androidx.datastore:datastore-rxjava2:1.3.0-alpha04") + docs("androidx.datastore:datastore-rxjava3:1.3.0-alpha04") docs("androidx.documentfile:documentfile:1.1.0") docs("androidx.draganddrop:draganddrop:1.0.0") docs("androidx.drawerlayout:drawerlayout:1.2.0") @@ -184,6 +185,8 @@ dependencies { docs("androidx.fragment:fragment-compose:1.8.9") docs("androidx.fragment:fragment-ktx:1.8.9") docs("androidx.fragment:fragment-testing:1.8.9") + docs("androidx.glance.wear:wear:1.0.0-alpha01") + docs("androidx.glance.wear:wear-core:1.0.0-alpha01") docs("androidx.glance:glance:1.2.0-rc01") docs("androidx.glance:glance-appwidget:1.2.0-rc01") docs("androidx.glance:glance-appwidget-multiprocess:1.2.0-rc01") @@ -283,7 +286,7 @@ dependencies { docs("androidx.mediarouter:mediarouter:1.8.1") docs("androidx.mediarouter:mediarouter-testing:1.8.1") docs("androidx.metrics:metrics-performance:1.0.0") - kmpDocs("androidx.navigation3:navigation3-ui:1.1.0-alpha01") + kmpDocs("androidx.navigation3:navigation3-ui:1.1.0-alpha02") kmpDocs("androidx.navigation:navigation-common:2.9.6") docs("androidx.navigation:navigation-common-ktx:2.9.6") kmpDocs("androidx.navigation:navigation-compose:2.9.6") @@ -297,20 +300,20 @@ dependencies { kmpDocs("androidx.navigation:navigation-testing:2.9.6") docs("androidx.navigation:navigation-ui:2.9.6") docs("androidx.navigation:navigation-ui-ktx:2.9.6") - kmpDocs("androidx.navigation3:navigation3-runtime:1.1.0-alpha01") + kmpDocs("androidx.navigation3:navigation3-runtime:1.1.0-alpha02") kmpDocs("androidx.navigationevent:navigationevent:1.0.1") kmpDocs("androidx.navigationevent:navigationevent-compose:1.0.1") kmpDocs("androidx.navigationevent:navigationevent-testing:1.0.1") - kmpDocs("androidx.paging:paging-common:3.4.0-beta01") - docs("androidx.paging:paging-common-ktx:3.4.0-beta01") - kmpDocs("androidx.paging:paging-compose:3.4.0-beta01") - docs("androidx.paging:paging-guava:3.4.0-beta01") - docs("androidx.paging:paging-runtime:3.4.0-beta01") - docs("androidx.paging:paging-runtime-ktx:3.4.0-beta01") - docs("androidx.paging:paging-rxjava2:3.4.0-beta01") - docs("androidx.paging:paging-rxjava2-ktx:3.4.0-beta01") - docs("androidx.paging:paging-rxjava3:3.4.0-beta01") - kmpDocs("androidx.paging:paging-testing:3.4.0-beta01") + kmpDocs("androidx.paging:paging-common:3.4.0-rc01") + docs("androidx.paging:paging-common-ktx:3.4.0-rc01") + kmpDocs("androidx.paging:paging-compose:3.4.0-rc01") + docs("androidx.paging:paging-guava:3.4.0-rc01") + docs("androidx.paging:paging-runtime:3.4.0-rc01") + docs("androidx.paging:paging-runtime-ktx:3.4.0-rc01") + docs("androidx.paging:paging-rxjava2:3.4.0-rc01") + docs("androidx.paging:paging-rxjava2-ktx:3.4.0-rc01") + docs("androidx.paging:paging-rxjava3:3.4.0-rc01") + kmpDocs("androidx.paging:paging-testing:3.4.0-rc01") docs("androidx.palette:palette:1.0.0") docs("androidx.palette:palette-ktx:1.0.0") docs("androidx.pdf:pdf-compose:1.0.0-alpha12") @@ -407,8 +410,8 @@ dependencies { docs("androidx.tracing:tracing-ktx:1.3.0") docs("androidx.tracing:tracing-perfetto:1.0.1") docs("androidx.tracing:tracing-perfetto-handshake:1.0.1") - docs("androidx.transition:transition:1.7.0-rc01") - docs("androidx.transition:transition-ktx:1.7.0-rc01") + docs("androidx.transition:transition:1.7.0") + docs("androidx.transition:transition-ktx:1.7.0") docs("androidx.tv:tv-foundation:1.0.0-alpha12") docs("androidx.tv:tv-material:1.1.0-alpha01") docs("androidx.tvprovider:tvprovider:1.1.0") @@ -418,27 +421,27 @@ dependencies { docs("androidx.versionedparcelable:versionedparcelable:1.2.1") docs("androidx.viewpager2:viewpager2:1.1.0") docs("androidx.viewpager:viewpager:1.1.0") - docs("androidx.wear.compose:compose-foundation:1.6.0-alpha07") - docs("androidx.wear.compose:compose-material:1.6.0-alpha07") - docs("androidx.wear.compose:compose-material-core:1.6.0-alpha07") - docs("androidx.wear.compose:compose-material3:1.6.0-alpha07") - docs("androidx.wear.compose:compose-navigation:1.6.0-alpha07") - docs("androidx.wear.compose:compose-navigation3:1.6.0-alpha07") - docs("androidx.wear.compose:compose-ui-tooling:1.6.0-alpha07") - docs("androidx.wear.protolayout:protolayout:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-expression:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-material:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-material-core:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-material3:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-renderer:1.4.0-alpha03") - docs("androidx.wear.protolayout:protolayout-testing:1.4.0-alpha03") - docs("androidx.wear.tiles:tiles:1.6.0-alpha03") - docs("androidx.wear.tiles:tiles-material:1.6.0-alpha03") - docs("androidx.wear.tiles:tiles-renderer:1.6.0-alpha03") - docs("androidx.wear.tiles:tiles-testing:1.6.0-alpha03") - docs("androidx.wear.tiles:tiles-tooling:1.6.0-alpha03") - docs("androidx.wear.tiles:tiles-tooling-preview:1.6.0-alpha03") + docs("androidx.wear.compose:compose-foundation:1.6.0-alpha08") + docs("androidx.wear.compose:compose-material:1.6.0-alpha08") + docs("androidx.wear.compose:compose-material-core:1.6.0-alpha08") + docs("androidx.wear.compose:compose-material3:1.6.0-alpha08") + docs("androidx.wear.compose:compose-navigation:1.6.0-alpha08") + docs("androidx.wear.compose:compose-navigation3:1.6.0-alpha08") + docs("androidx.wear.compose:compose-ui-tooling:1.6.0-alpha08") + docs("androidx.wear.protolayout:protolayout:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-expression:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-material:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-material-core:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-material3:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-renderer:1.4.0-alpha04") + docs("androidx.wear.protolayout:protolayout-testing:1.4.0-alpha04") + docs("androidx.wear.tiles:tiles:1.6.0-alpha04") + docs("androidx.wear.tiles:tiles-material:1.6.0-alpha04") + docs("androidx.wear.tiles:tiles-renderer:1.6.0-alpha04") + docs("androidx.wear.tiles:tiles-testing:1.6.0-alpha04") + docs("androidx.wear.tiles:tiles-tooling:1.6.0-alpha04") + docs("androidx.wear.tiles:tiles-tooling-preview:1.6.0-alpha04") docs("androidx.wear.watchface:watchface:1.3.0-alpha07") docs("androidx.wear.watchface:watchface-client:1.3.0-alpha07") docs("androidx.wear.watchface:watchface-client-guava:1.3.0-alpha07") @@ -462,8 +465,8 @@ dependencies { docs("androidx.wear:wear-phone-interactions:1.1.0") docs("androidx.wear:wear-remote-interactions:1.2.0-beta01") docs("androidx.wear:wear-tooling-preview:1.0.0") - docs("androidx.webgpu:webgpu:1.0.0-alpha02") - docs("androidx.webkit:webkit:1.15.0") + docs("androidx.webgpu:webgpu:1.0.0-alpha03") + docs("androidx.webkit:webkit:1.16.0-alpha01") docs("androidx.window.extensions.core:core:1.0.0") docs("androidx.window:window:1.6.0-alpha01") stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"])) @@ -491,7 +494,7 @@ dependencies { docs("androidx.xr.compose.material3:material3:1.0.0-alpha13") docs("androidx.xr.compose:compose:1.0.0-alpha09") docs("androidx.xr.compose:compose-testing:1.0.0-alpha09") - docs("androidx.xr.glimmer:glimmer:1.0.0-alpha03") + docs("androidx.xr.glimmer:glimmer:1.0.0-alpha04") docs("androidx.xr.projected:projected:1.0.0-alpha03") docs("androidx.xr.runtime:runtime:1.0.0-alpha09") docs("androidx.xr.runtime:runtime-manifest:1.0.0-alpha09") diff --git a/health/connect/connect-client/api/1.2.0-beta01.txt b/health/connect/connect-client/api/1.2.0-beta01.txt index c43af2c355c2b..49c0e6eda73c4 100644 --- a/health/connect/connect-client/api/1.2.0-beta01.txt +++ b/health/connect/connect-client/api/1.2.0-beta01.txt @@ -289,7 +289,7 @@ package androidx.health.connect.client.records { field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1 field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion; field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } @@ -298,7 +298,7 @@ package androidx.health.connect.client.records { property public static int ACTIVITY_INTENSITY_TYPE_MODERATE; property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS; property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } diff --git a/health/connect/connect-client/api/current.ignore b/health/connect/connect-client/api/current.ignore index 1e6f42aa5003c..681f27c33f606 100644 --- a/health/connect/connect-client/api/current.ignore +++ b/health/connect/connect-client/api/current.ignore @@ -1,811 +1,3 @@ // Baseline format: 1.0 -BecameUnchecked: androidx.health.connect.client.feature: - Removed package androidx.health.connect.client.feature from compatibility checked API surface - - -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#readRecord(androidx.health.connect.client.HealthConnectClient, String, kotlin.coroutines.Continuation>): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.readRecord(androidx.health.connect.client.HealthConnectClient,String,kotlin.coroutines.Continuation>) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.permission.HealthPermission.Companion#getReadPermission(): - Binary breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getReadPermission() has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.permission.HealthPermission.Companion#getWritePermission(): - Binary breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getWritePermission() has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.request.ReadRecordsRequestKt#ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter, java.util.Set, boolean, int, String): - Binary breaking change: method androidx.health.connect.client.request.ReadRecordsRequestKt.ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter,java.util.Set,boolean,int,String) has been removed from bytecode - - -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#readRecord(androidx.health.connect.client.HealthConnectClient, String, kotlin.coroutines.Continuation>): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.readRecord(androidx.health.connect.client.HealthConnectClient,String,kotlin.coroutines.Continuation>) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.permission.HealthPermission.Companion#getReadPermission(): - Source breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getReadPermission() can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.permission.HealthPermission.Companion#getWritePermission(): - Source breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getWritePermission() can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.request.ReadRecordsRequestKt#ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter, java.util.Set, boolean, int, String): - Source breaking change: method androidx.health.connect.client.request.ReadRecordsRequestKt.ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter,java.util.Set,boolean,int,String) can no longer be resolved from Java source - - -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getFeatures(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getFeatures() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getHealthConnectSettingsAction(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getHealthConnectSettingsAction() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getPermissionController(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getPermissionController() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient.Companion#getHealthConnectSettingsAction(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.Companion.getHealthConnectSettingsAction() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResult#getDataOrigins(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResult.getDataOrigins() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getEndTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getResult(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getStartTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getEndTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getResult(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getStartTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.changes.DeletionChange#getRecordId(): - Source breaking change: method androidx.health.connect.client.changes.DeletionChange.getRecordId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.changes.UpsertionChange#getRecord(): - Source breaking change: method androidx.health.connect.client.changes.UpsertionChange.getRecord() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getTemperature(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getTemperature() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getBasalMetabolicRate(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getBasalMetabolicRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getLevel(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getLevel() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getMealType(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getMealType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getRelationToMeal(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getRelationToMeal() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getSpecimenSource(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getSpecimenSource() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getBodyPosition(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getBodyPosition() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getDiastolic(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getDiastolic() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getSystolic(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getSystolic() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getPercentage(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getPercentage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getTemperature(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getTemperature() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getAppearance(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getAppearance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getSensation(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getSensation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample#getRevolutionsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample.getRevolutionsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getDistance(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getElevation(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getElevation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.ActiveCaloriesBurnedGoal#getActiveCalories(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.ActiveCaloriesBurnedGoal.getActiveCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal#getDistance(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal#getDuration(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal.getDuration() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceGoal#getDistance(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceGoal.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DurationGoal#getDuration(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DurationGoal.getDuration() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.RepetitionsGoal#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.RepetitionsGoal.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.StepsGoal#getSteps(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.StepsGoal.getSteps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.TotalCaloriesBurnedGoal#getTotalCalories(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.TotalCaloriesBurnedGoal.getTotalCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getLength(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getLength() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget#getMaxCadence(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget.getMaxCadence() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget#getMinCadence(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget.getMinCadence() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget#getMaxHeartRate(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget.getMaxHeartRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget#getMinHeartRate(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget.getMinHeartRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget#getMaxPower(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget.getMaxPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget#getMinPower(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget.getMinPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.RateOfPerceivedExertionTarget#getRpe(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.RateOfPerceivedExertionTarget.getRpe() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget#getMaxSpeed(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget.getMaxSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget#getMinSpeed(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget.getMinSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.WeightTarget#getMass(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.WeightTarget.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute#getRoute(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.getRoute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getAltitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getAltitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getHorizontalAccuracy(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getHorizontalAccuracy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getLatitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getLatitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getLongitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getLongitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getVerticalAccuracy(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getVerticalAccuracy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRouteResult.Data#getExerciseRoute(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRouteResult.Data.getExerciseRoute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getSegmentType(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getSegmentType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getExerciseRouteResult(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getExerciseRouteResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getLaps(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getLaps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getPlannedExerciseSessionId(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getPlannedExerciseSessionId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getSegments(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getSegments() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getFloors(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getFloors() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord.Sample#getBeatsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.Sample.getBeatsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getHeartRateVariabilityMillis(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getHeartRateVariabilityMillis() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getHeight(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getHeight() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getVolume(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getVolume() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getFlow(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getFlow() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getBiotin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getBiotin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCaffeine(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCaffeine() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCalcium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCalcium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getChloride(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getChloride() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCholesterol(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCholesterol() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getChromium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getChromium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCopper(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCopper() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getDietaryFiber(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getDietaryFiber() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEnergyFromFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEnergyFromFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getFolate(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getFolate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getFolicAcid(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getFolicAcid() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getIodine(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getIodine() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getIron(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getIron() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMagnesium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMagnesium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getManganese(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getManganese() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMealType(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMealType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMolybdenum(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMolybdenum() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMonounsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMonounsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getName(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getName() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getNiacin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getNiacin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPantothenicAcid(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPantothenicAcid() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPhosphorus(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPhosphorus() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPolyunsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPolyunsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPotassium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPotassium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getProtein(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getProtein() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getRiboflavin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getRiboflavin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSelenium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSelenium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSodium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSodium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSugar(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSugar() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getThiamin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getThiamin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTotalCarbohydrate(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTotalCarbohydrate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTotalFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTotalFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTransFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTransFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getUnsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getUnsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminA(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminA() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminB12(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminB12() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminB6(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminB6() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminC(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminC() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminD(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminD() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminE(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminE() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminK(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminK() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getZinc(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getZinc() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getResult(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getPercentage(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getPercentage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getDescription(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getDescription() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getSteps(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getSteps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getBlocks(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getBlocks() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getCompletedExerciseSessionId(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getCompletedExerciseSessionId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#hasExplicitTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.hasExplicitTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getCompletionGoal(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getCompletionGoal() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getDescription(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getDescription() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getExercisePhase(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getExercisePhase() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getPerformanceTargets(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getPerformanceTargets() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord.Sample#getPower(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.Sample.getPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Record#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.Record.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getRate(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getBeatsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getBeatsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getProtectionUsed(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getProtectionUsed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getBaseline(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getBaseline() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getDeltas(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getDeltas() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord.Delta#getDelta(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.Delta.getDelta() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord.Delta#getTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.Delta.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStages(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStages() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getStage(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getStage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord.Sample#getSpeed(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.Sample.getSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord.Sample#getRate(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.Sample.getRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getCount(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getCount() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getMeasurementMethod(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getMeasurementMethod() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getVo2MillilitersPerMinuteKilogram(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getVo2MillilitersPerMinuteKilogram() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getWeight(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getWeight() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getCount(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getCount() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.DataOrigin#getPackageName(): - Source breaking change: method androidx.health.connect.client.records.metadata.DataOrigin.getPackageName() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getManufacturer(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getManufacturer() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getModel(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getModel() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getType(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getClientRecordId(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getClientRecordId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getClientRecordVersion(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getClientRecordVersion() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getDataOrigin(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getDataOrigin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getDevice(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getDevice() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getId(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getLastModifiedTime(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getLastModifiedTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getRecordingMethod(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getRecordingMethod() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getChanges(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getChanges() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getChangesTokenExpired(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getChangesTokenExpired() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getNextChangesToken(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getNextChangesToken() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#hasMore(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.hasMore() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.InsertRecordsResponse#getRecordIdsList(): - Source breaking change: method androidx.health.connect.client.response.InsertRecordsResponse.getRecordIdsList() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordResponse#getRecord(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordResponse.getRecord() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordsResponse#getPageToken(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordsResponse.getPageToken() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordsResponse#getRecords(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordsResponse.getRecords() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.BloodGlucose#getMilligramsPerDeciliter(): - Source breaking change: method androidx.health.connect.client.units.BloodGlucose.getMilligramsPerDeciliter() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.BloodGlucose#getMillimolesPerLiter(): - Source breaking change: method androidx.health.connect.client.units.BloodGlucose.getMillimolesPerLiter() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getCalories(): - Source breaking change: method androidx.health.connect.client.units.Energy.getCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getJoules(): - Source breaking change: method androidx.health.connect.client.units.Energy.getJoules() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getKilocalories(): - Source breaking change: method androidx.health.connect.client.units.Energy.getKilocalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getKilojoules(): - Source breaking change: method androidx.health.connect.client.units.Energy.getKilojoules() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getFeet(): - Source breaking change: method androidx.health.connect.client.units.Length.getFeet() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getInches(): - Source breaking change: method androidx.health.connect.client.units.Length.getInches() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getKilometers(): - Source breaking change: method androidx.health.connect.client.units.Length.getKilometers() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getMeters(): - Source breaking change: method androidx.health.connect.client.units.Length.getMeters() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getMiles(): - Source breaking change: method androidx.health.connect.client.units.Length.getMiles() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getGrams(): - Source breaking change: method androidx.health.connect.client.units.Mass.getGrams() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getKilograms(): - Source breaking change: method androidx.health.connect.client.units.Mass.getKilograms() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getMicrograms(): - Source breaking change: method androidx.health.connect.client.units.Mass.getMicrograms() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getMilligrams(): - Source breaking change: method androidx.health.connect.client.units.Mass.getMilligrams() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getOunces(): - Source breaking change: method androidx.health.connect.client.units.Mass.getOunces() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getPounds(): - Source breaking change: method androidx.health.connect.client.units.Mass.getPounds() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Percentage#getValue(): - Source breaking change: method androidx.health.connect.client.units.Percentage.getValue() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Power#getKilocaloriesPerDay(): - Source breaking change: method androidx.health.connect.client.units.Power.getKilocaloriesPerDay() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Power#getWatts(): - Source breaking change: method androidx.health.connect.client.units.Power.getWatts() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Pressure#getMillimetersOfMercury(): - Source breaking change: method androidx.health.connect.client.units.Pressure.getMillimetersOfMercury() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Temperature#getCelsius(): - Source breaking change: method androidx.health.connect.client.units.Temperature.getCelsius() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Temperature#getFahrenheit(): - Source breaking change: method androidx.health.connect.client.units.Temperature.getFahrenheit() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.TemperatureDelta#getCelsius(): - Source breaking change: method androidx.health.connect.client.units.TemperatureDelta.getCelsius() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.TemperatureDelta#getFahrenheit(): - Source breaking change: method androidx.health.connect.client.units.TemperatureDelta.getFahrenheit() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getKilometersPerHour(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getKilometersPerHour() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getMetersPerSecond(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getMetersPerSecond() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getMilesPerHour(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getMilesPerHour() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getFluidOuncesUs(): - Source breaking change: method androidx.health.connect.client.units.Volume.getFluidOuncesUs() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getLiters(): - Source breaking change: method androidx.health.connect.client.units.Volume.getLiters() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getMilliliters(): - Source breaking change: method androidx.health.connect.client.units.Volume.getMilliliters() can no longer be resolved from Kotlin source - - -RemovedMethod: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Binary breaking change: Removed method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) -RemovedMethod: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Binary breaking change: Removed method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) -RemovedMethod: androidx.health.connect.client.time.TimeRangeFilter#TimeRangeFilter(): - Binary breaking change: Removed constructor androidx.health.connect.client.time.TimeRangeFilter() +ChangedType: androidx.health.connect.client.records.ActivityIntensityRecord#INTENSITY_MINUTES_TOTAL: + Binary breaking change: Field androidx.health.connect.client.records.ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL has changed type from androidx.health.connect.client.aggregate.AggregateMetric to androidx.health.connect.client.aggregate.AggregateMetric diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt index c43af2c355c2b..49c0e6eda73c4 100644 --- a/health/connect/connect-client/api/current.txt +++ b/health/connect/connect-client/api/current.txt @@ -289,7 +289,7 @@ package androidx.health.connect.client.records { field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1 field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion; field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } @@ -298,7 +298,7 @@ package androidx.health.connect.client.records { property public static int ACTIVITY_INTENSITY_TYPE_MODERATE; property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS; property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } diff --git a/health/connect/connect-client/api/restricted_1.2.0-beta01.txt b/health/connect/connect-client/api/restricted_1.2.0-beta01.txt index 6a526f78a51b3..51480ab736d82 100644 --- a/health/connect/connect-client/api/restricted_1.2.0-beta01.txt +++ b/health/connect/connect-client/api/restricted_1.2.0-beta01.txt @@ -289,7 +289,7 @@ package androidx.health.connect.client.records { field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1 field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion; field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } @@ -298,7 +298,7 @@ package androidx.health.connect.client.records { property public static int ACTIVITY_INTENSITY_TYPE_MODERATE; property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS; property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } diff --git a/health/connect/connect-client/api/restricted_current.ignore b/health/connect/connect-client/api/restricted_current.ignore index edfd0fd74166b..681f27c33f606 100644 --- a/health/connect/connect-client/api/restricted_current.ignore +++ b/health/connect/connect-client/api/restricted_current.ignore @@ -1,825 +1,3 @@ // Baseline format: 1.0 -BecameUnchecked: androidx.health.connect.client.feature: - Removed package androidx.health.connect.client.feature from compatibility checked API surface - - -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.HealthConnectClientExt#readRecord(androidx.health.connect.client.HealthConnectClient, String, kotlin.coroutines.Continuation>): - Binary breaking change: method androidx.health.connect.client.HealthConnectClientExt.readRecord(androidx.health.connect.client.HealthConnectClient,String,kotlin.coroutines.Continuation>) has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.permission.HealthPermission.Companion#getReadPermission(): - Binary breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getReadPermission() has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.permission.HealthPermission.Companion#getWritePermission(): - Binary breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getWritePermission() has been removed from bytecode -RemovedFromBytecode: androidx.health.connect.client.request.ReadRecordsRequestKt#ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter, java.util.Set, boolean, int, String): - Binary breaking change: method androidx.health.connect.client.request.ReadRecordsRequestKt.ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter,java.util.Set,boolean,int,String) has been removed from bytecode - - -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.HealthConnectClientExt#readRecord(androidx.health.connect.client.HealthConnectClient, String, kotlin.coroutines.Continuation>): - Source breaking change: method androidx.health.connect.client.HealthConnectClientExt.readRecord(androidx.health.connect.client.HealthConnectClient,String,kotlin.coroutines.Continuation>) can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.permission.HealthPermission.Companion#getReadPermission(): - Source breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getReadPermission() can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.permission.HealthPermission.Companion#getWritePermission(): - Source breaking change: method androidx.health.connect.client.permission.HealthPermission.Companion.getWritePermission() can no longer be resolved from Java source -RemovedFromJava: androidx.health.connect.client.request.ReadRecordsRequestKt#ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter, java.util.Set, boolean, int, String): - Source breaking change: method androidx.health.connect.client.request.ReadRecordsRequestKt.ReadRecordsRequest(androidx.health.connect.client.time.TimeRangeFilter,java.util.Set,boolean,int,String) can no longer be resolved from Java source - - -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getFeatures(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getFeatures() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getHealthConnectSettingsAction(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getHealthConnectSettingsAction() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient#getPermissionController(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.getPermissionController() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.HealthConnectClient.Companion#getHealthConnectSettingsAction(): - Source breaking change: method androidx.health.connect.client.HealthConnectClient.Companion.getHealthConnectSettingsAction() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResult#getDataOrigins(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResult.getDataOrigins() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getEndTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getResult(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getStartTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getEndTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getResult(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod#getStartTime(): - Source breaking change: method androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.changes.DeletionChange#getRecordId(): - Source breaking change: method androidx.health.connect.client.changes.DeletionChange.getRecordId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.changes.UpsertionChange#getRecord(): - Source breaking change: method androidx.health.connect.client.changes.UpsertionChange.getRecord() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ActiveCaloriesBurnedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ActiveCaloriesBurnedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getTemperature(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getTemperature() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalBodyTemperatureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BasalBodyTemperatureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getBasalMetabolicRate(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getBasalMetabolicRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BasalMetabolicRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BasalMetabolicRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getLevel(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getLevel() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getMealType(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getMealType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getRelationToMeal(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getRelationToMeal() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getSpecimenSource(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getSpecimenSource() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodGlucoseRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BloodGlucoseRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getBodyPosition(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getBodyPosition() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getDiastolic(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getDiastolic() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getSystolic(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getSystolic() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BloodPressureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BloodPressureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getPercentage(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getPercentage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyFatRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyFatRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getTemperature(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getTemperature() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyTemperatureRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyTemperatureRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BodyWaterMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BodyWaterMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.BoneMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.BoneMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getAppearance(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getAppearance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getSensation(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getSensation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CervicalMucusRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CervicalMucusRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample#getRevolutionsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample.getRevolutionsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getDistance(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.DistanceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.DistanceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getElevation(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getElevation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ElevationGainedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ElevationGainedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.ActiveCaloriesBurnedGoal#getActiveCalories(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.ActiveCaloriesBurnedGoal.getActiveCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal#getDistance(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal#getDuration(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceAndDurationGoal.getDuration() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceGoal#getDistance(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DistanceGoal.getDistance() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.DurationGoal#getDuration(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.DurationGoal.getDuration() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.RepetitionsGoal#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.RepetitionsGoal.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.StepsGoal#getSteps(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.StepsGoal.getSteps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseCompletionGoal.TotalCaloriesBurnedGoal#getTotalCalories(): - Source breaking change: method androidx.health.connect.client.records.ExerciseCompletionGoal.TotalCaloriesBurnedGoal.getTotalCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getLength(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getLength() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseLap#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseLap.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget#getMaxCadence(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget.getMaxCadence() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget#getMinCadence(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.CadenceTarget.getMinCadence() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget#getMaxHeartRate(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget.getMaxHeartRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget#getMinHeartRate(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.HeartRateTarget.getMinHeartRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget#getMaxPower(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget.getMaxPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget#getMinPower(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.PowerTarget.getMinPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.RateOfPerceivedExertionTarget#getRpe(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.RateOfPerceivedExertionTarget.getRpe() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget#getMaxSpeed(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget.getMaxSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget#getMinSpeed(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.SpeedTarget.getMinSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExercisePerformanceTarget.WeightTarget#getMass(): - Source breaking change: method androidx.health.connect.client.records.ExercisePerformanceTarget.WeightTarget.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute#getRoute(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.getRoute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getAltitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getAltitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getHorizontalAccuracy(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getHorizontalAccuracy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getLatitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getLatitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getLongitude(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getLongitude() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRoute.Location#getVerticalAccuracy(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRoute.Location.getVerticalAccuracy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseRouteResult.Data#getExerciseRoute(): - Source breaking change: method androidx.health.connect.client.records.ExerciseRouteResult.Data.getExerciseRoute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getSegmentType(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getSegmentType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSegment#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSegment.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getExerciseRouteResult(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getExerciseRouteResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getLaps(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getLaps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getPlannedExerciseSessionId(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getPlannedExerciseSessionId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getSegments(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getSegments() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.ExerciseSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.ExerciseSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getFloors(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getFloors() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.FloorsClimbedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.FloorsClimbedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord.Sample#getBeatsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.Sample.getBeatsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getHeartRateVariabilityMillis(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getHeartRateVariabilityMillis() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getHeight(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getHeight() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HeightRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HeightRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.HydrationRecord#getVolume(): - Source breaking change: method androidx.health.connect.client.records.HydrationRecord.getVolume() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.InstantaneousRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.InstantaneousRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.InstantaneousRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.InstantaneousRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntermenstrualBleedingRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.IntermenstrualBleedingRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntervalRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.IntervalRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntervalRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.IntervalRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntervalRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.IntervalRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.IntervalRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.IntervalRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getMass(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getMass() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.LeanBodyMassRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.LeanBodyMassRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getFlow(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getFlow() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationFlowRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationFlowRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.MenstruationPeriodRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.MenstruationPeriodRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getBiotin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getBiotin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCaffeine(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCaffeine() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCalcium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCalcium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getChloride(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getChloride() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCholesterol(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCholesterol() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getChromium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getChromium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getCopper(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getCopper() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getDietaryFiber(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getDietaryFiber() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getEnergyFromFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getEnergyFromFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getFolate(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getFolate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getFolicAcid(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getFolicAcid() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getIodine(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getIodine() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getIron(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getIron() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMagnesium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMagnesium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getManganese(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getManganese() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMealType(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMealType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMolybdenum(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMolybdenum() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getMonounsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getMonounsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getName(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getName() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getNiacin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getNiacin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPantothenicAcid(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPantothenicAcid() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPhosphorus(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPhosphorus() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPolyunsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPolyunsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getPotassium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getPotassium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getProtein(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getProtein() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getRiboflavin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getRiboflavin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSelenium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSelenium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSodium(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSodium() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getSugar(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getSugar() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getThiamin(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getThiamin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTotalCarbohydrate(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTotalCarbohydrate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTotalFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTotalFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getTransFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getTransFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getUnsaturatedFat(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getUnsaturatedFat() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminA(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminA() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminB12(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminB12() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminB6(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminB6() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminC(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminC() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminD(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminD() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminE(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminE() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getVitaminK(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getVitaminK() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.NutritionRecord#getZinc(): - Source breaking change: method androidx.health.connect.client.records.NutritionRecord.getZinc() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getResult(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getResult() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OvulationTestRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.OvulationTestRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getPercentage(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getPercentage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.OxygenSaturationRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.OxygenSaturationRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getDescription(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getDescription() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getRepetitions(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getRepetitions() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseBlock#getSteps(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseBlock.getSteps() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getBlocks(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getBlocks() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getCompletedExerciseSessionId(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getCompletedExerciseSessionId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseSessionRecord#hasExplicitTime(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseSessionRecord.hasExplicitTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getCompletionGoal(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getCompletionGoal() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getDescription(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getDescription() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getExercisePhase(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getExercisePhase() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getExerciseType(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getExerciseType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PlannedExerciseStep#getPerformanceTargets(): - Source breaking change: method androidx.health.connect.client.records.PlannedExerciseStep.getPerformanceTargets() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord.Sample#getPower(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.Sample.getPower() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.PowerRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.PowerRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Record#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.Record.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getRate(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RespiratoryRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.RespiratoryRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getBeatsPerMinute(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getBeatsPerMinute() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.RestingHeartRateRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.RestingHeartRateRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SeriesRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.SeriesRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getProtectionUsed(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getProtectionUsed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SexualActivityRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SexualActivityRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getBaseline(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getBaseline() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getDeltas(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getDeltas() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getMeasurementLocation(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getMeasurementLocation() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord.Delta#getDelta(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.Delta.getDelta() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SkinTemperatureRecord.Delta#getTime(): - Source breaking change: method androidx.health.connect.client.records.SkinTemperatureRecord.Delta.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getNotes(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getNotes() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStages(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStages() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord#getTitle(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.getTitle() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getStage(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getStage() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SleepSessionRecord.Stage#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SleepSessionRecord.Stage.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord.Sample#getSpeed(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.Sample.getSpeed() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.SpeedRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.SpeedRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getSamples(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getSamples() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord.Sample#getRate(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.Sample.getRate() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsCadenceRecord.Sample#getTime(): - Source breaking change: method androidx.health.connect.client.records.StepsCadenceRecord.Sample.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getCount(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getCount() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.StepsRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.StepsRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getEnergy(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getEnergy() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.TotalCaloriesBurnedRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.TotalCaloriesBurnedRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getMeasurementMethod(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getMeasurementMethod() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getVo2MillilitersPerMinuteKilogram(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getVo2MillilitersPerMinuteKilogram() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.Vo2MaxRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.Vo2MaxRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getTime(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getWeight(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getWeight() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WeightRecord#getZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WeightRecord.getZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getCount(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getCount() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getEndTime(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getEndTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getEndZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getEndZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getMetadata(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getMetadata() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getStartTime(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getStartTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.WheelchairPushesRecord#getStartZoneOffset(): - Source breaking change: method androidx.health.connect.client.records.WheelchairPushesRecord.getStartZoneOffset() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.DataOrigin#getPackageName(): - Source breaking change: method androidx.health.connect.client.records.metadata.DataOrigin.getPackageName() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getManufacturer(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getManufacturer() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getModel(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getModel() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Device#getType(): - Source breaking change: method androidx.health.connect.client.records.metadata.Device.getType() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getClientRecordId(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getClientRecordId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getClientRecordVersion(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getClientRecordVersion() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getDataOrigin(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getDataOrigin() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getDevice(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getDevice() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getId(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getId() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getLastModifiedTime(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getLastModifiedTime() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.records.metadata.Metadata#getRecordingMethod(): - Source breaking change: method androidx.health.connect.client.records.metadata.Metadata.getRecordingMethod() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getChanges(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getChanges() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getChangesTokenExpired(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getChangesTokenExpired() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#getNextChangesToken(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.getNextChangesToken() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ChangesResponse#hasMore(): - Source breaking change: method androidx.health.connect.client.response.ChangesResponse.hasMore() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.InsertRecordsResponse#getRecordIdsList(): - Source breaking change: method androidx.health.connect.client.response.InsertRecordsResponse.getRecordIdsList() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordResponse#getRecord(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordResponse.getRecord() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordsResponse#getPageToken(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordsResponse.getPageToken() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.response.ReadRecordsResponse#getRecords(): - Source breaking change: method androidx.health.connect.client.response.ReadRecordsResponse.getRecords() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.BloodGlucose#getMilligramsPerDeciliter(): - Source breaking change: method androidx.health.connect.client.units.BloodGlucose.getMilligramsPerDeciliter() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.BloodGlucose#getMillimolesPerLiter(): - Source breaking change: method androidx.health.connect.client.units.BloodGlucose.getMillimolesPerLiter() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getCalories(): - Source breaking change: method androidx.health.connect.client.units.Energy.getCalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getJoules(): - Source breaking change: method androidx.health.connect.client.units.Energy.getJoules() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getKilocalories(): - Source breaking change: method androidx.health.connect.client.units.Energy.getKilocalories() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Energy#getKilojoules(): - Source breaking change: method androidx.health.connect.client.units.Energy.getKilojoules() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getFeet(): - Source breaking change: method androidx.health.connect.client.units.Length.getFeet() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getInches(): - Source breaking change: method androidx.health.connect.client.units.Length.getInches() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getKilometers(): - Source breaking change: method androidx.health.connect.client.units.Length.getKilometers() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getMeters(): - Source breaking change: method androidx.health.connect.client.units.Length.getMeters() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Length#getMiles(): - Source breaking change: method androidx.health.connect.client.units.Length.getMiles() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getGrams(): - Source breaking change: method androidx.health.connect.client.units.Mass.getGrams() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getKilograms(): - Source breaking change: method androidx.health.connect.client.units.Mass.getKilograms() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getMicrograms(): - Source breaking change: method androidx.health.connect.client.units.Mass.getMicrograms() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getMilligrams(): - Source breaking change: method androidx.health.connect.client.units.Mass.getMilligrams() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getOunces(): - Source breaking change: method androidx.health.connect.client.units.Mass.getOunces() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Mass#getPounds(): - Source breaking change: method androidx.health.connect.client.units.Mass.getPounds() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Percentage#getValue(): - Source breaking change: method androidx.health.connect.client.units.Percentage.getValue() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Power#getKilocaloriesPerDay(): - Source breaking change: method androidx.health.connect.client.units.Power.getKilocaloriesPerDay() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Power#getWatts(): - Source breaking change: method androidx.health.connect.client.units.Power.getWatts() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Pressure#getMillimetersOfMercury(): - Source breaking change: method androidx.health.connect.client.units.Pressure.getMillimetersOfMercury() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Temperature#getCelsius(): - Source breaking change: method androidx.health.connect.client.units.Temperature.getCelsius() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Temperature#getFahrenheit(): - Source breaking change: method androidx.health.connect.client.units.Temperature.getFahrenheit() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.TemperatureDelta#getCelsius(): - Source breaking change: method androidx.health.connect.client.units.TemperatureDelta.getCelsius() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.TemperatureDelta#getFahrenheit(): - Source breaking change: method androidx.health.connect.client.units.TemperatureDelta.getFahrenheit() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getKilometersPerHour(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getKilometersPerHour() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getMetersPerSecond(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getMetersPerSecond() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Velocity#getMilesPerHour(): - Source breaking change: method androidx.health.connect.client.units.Velocity.getMilesPerHour() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getFluidOuncesUs(): - Source breaking change: method androidx.health.connect.client.units.Volume.getFluidOuncesUs() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getLiters(): - Source breaking change: method androidx.health.connect.client.units.Volume.getLiters() can no longer be resolved from Kotlin source -RemovedFromKotlin: androidx.health.connect.client.units.Volume#getMilliliters(): - Source breaking change: method androidx.health.connect.client.units.Volume.getMilliliters() can no longer be resolved from Kotlin source - - -RemovedMethod: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, androidx.health.connect.client.time.TimeRangeFilter, kotlin.coroutines.Continuation): - Binary breaking change: Removed method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,androidx.health.connect.client.time.TimeRangeFilter,kotlin.coroutines.Continuation) -RemovedMethod: androidx.health.connect.client.HealthConnectClientExt#deleteRecords(androidx.health.connect.client.HealthConnectClient, java.util.List, java.util.List, kotlin.coroutines.Continuation): - Binary breaking change: Removed method androidx.health.connect.client.HealthConnectClientExt.deleteRecords(androidx.health.connect.client.HealthConnectClient,java.util.List,java.util.List,kotlin.coroutines.Continuation) -RemovedMethod: androidx.health.connect.client.time.TimeRangeFilter#TimeRangeFilter(): - Binary breaking change: Removed constructor androidx.health.connect.client.time.TimeRangeFilter() +ChangedType: androidx.health.connect.client.records.ActivityIntensityRecord#INTENSITY_MINUTES_TOTAL: + Binary breaking change: Field androidx.health.connect.client.records.ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL has changed type from androidx.health.connect.client.aggregate.AggregateMetric to androidx.health.connect.client.aggregate.AggregateMetric diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt index 6a526f78a51b3..51480ab736d82 100644 --- a/health/connect/connect-client/api/restricted_current.txt +++ b/health/connect/connect-client/api/restricted_current.txt @@ -289,7 +289,7 @@ package androidx.health.connect.client.records { field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1 field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion; field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } @@ -298,7 +298,7 @@ package androidx.health.connect.client.records { property public static int ACTIVITY_INTENSITY_TYPE_MODERATE; property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS; property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL; - property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; + property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL; property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL; } diff --git a/health/connect/connect-client/lint-baseline.xml b/health/connect/connect-client/lint-baseline.xml index e8e3305e88241..381356704607d 100644 --- a/health/connect/connect-client/lint-baseline.xml +++ b/health/connect/connect-client/lint-baseline.xml @@ -1,5 +1,23 @@ - + + + + + + + + + = 16) + + private val grantPermissionRule: GrantPermissionRule = + GrantPermissionRule.grant( + *listOf( + HealthPermission.getWritePermission(ActivityIntensityRecord::class), + HealthPermission.getReadPermission(ActivityIntensityRecord::class), + ) + .toTypedArray() + ) + + @get:Rule val ruleChain: RuleChain = RuleChain.outerRule(assumeRule).around(grantPermissionRule) + + @After + fun tearDown() = runTest { + healthConnectClient.deleteRecords( + ActivityIntensityRecord::class, + TimeRangeFilter.after(Instant.EPOCH), + ) + } + + // The test aims to verify the wiring with the framework rather than the aggregation logic + // itself. + @Test + fun aggregateRecords() = runTest { + val insertRecordsResponse = + healthConnectClient.insertRecords( + listOf( + ActivityIntensityRecord( + startTime = START_TIME, + startZoneOffset = ZoneOffset.UTC, + endTime = START_TIME.plusSeconds(150), + endZoneOffset = ZoneOffset.UTC, + metadata = Metadata.manualEntry(), + activityIntensityType = ACTIVITY_INTENSITY_TYPE_VIGOROUS, + ), + ActivityIntensityRecord( + startTime = START_TIME.plusSeconds(180), + startZoneOffset = ZoneOffset.UTC, + endTime = START_TIME.plusSeconds(300), + endZoneOffset = ZoneOffset.UTC, + metadata = Metadata.manualEntry(), + activityIntensityType = ACTIVITY_INTENSITY_TYPE_MODERATE, + ), + ) + ) + assertThat(insertRecordsResponse.recordIdsList.size).isEqualTo(2) + + val aggregateResponse = + healthConnectClient.aggregate( + AggregateRequest( + setOf( + ActivityIntensityRecord.DURATION_TOTAL, + ActivityIntensityRecord.MODERATE_DURATION_TOTAL, + ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL, + ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL, + ), + TimeRangeFilter.after(Instant.EPOCH), + ) + ) + + with(aggregateResponse) { + assertThat(this[ActivityIntensityRecord.DURATION_TOTAL]) + .isEqualTo(Duration.ofSeconds(270)) + assertThat(this[ActivityIntensityRecord.MODERATE_DURATION_TOTAL]) + .isEqualTo(Duration.ofSeconds(120)) + assertThat(this[ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL]) + .isEqualTo(Duration.ofSeconds(150)) + assertThat(this[ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL]).isEqualTo(7) + } + } +} diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt new file mode 100644 index 0000000000000..f56873dd24f54 --- /dev/null +++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2026 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.health.connect.client.impl.platform.records + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.os.ext.SdkExtensions +import androidx.health.connect.client.AssumeRule +import androidx.health.connect.client.HealthConnectClient +import androidx.health.connect.client.impl.HealthConnectClientUpsideDownImpl +import androidx.health.connect.client.permission.HealthPermission +import androidx.health.connect.client.records.MindfulnessSessionRecord +import androidx.health.connect.client.records.MindfulnessSessionRecord.Companion.MINDFULNESS_SESSION_TYPE_BREATHING +import androidx.health.connect.client.records.MindfulnessSessionRecord.Companion.MINDFULNESS_SESSION_TYPE_MEDITATION +import androidx.health.connect.client.records.metadata.Metadata +import androidx.health.connect.client.request.AggregateRequest +import androidx.health.connect.client.time.TimeRangeFilter +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import androidx.test.filters.SdkSuppress +import androidx.test.rule.GrantPermissionRule +import com.google.common.truth.Truth.assertThat +import java.time.Duration +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneOffset +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@MediumTest +@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +class MindfulnessSessionRecordTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + private val healthConnectClient: HealthConnectClient = + HealthConnectClientUpsideDownImpl(context) + + private companion object { + private val START_TIME = + LocalDate.now().minusDays(5).atStartOfDay().toInstant(ZoneOffset.UTC) + } + + private val assumeRule: AssumeRule = + AssumeRule(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15) + + private val grantPermissionRule: GrantPermissionRule = + GrantPermissionRule.grant( + *listOf( + HealthPermission.getWritePermission(MindfulnessSessionRecord::class), + HealthPermission.getReadPermission(MindfulnessSessionRecord::class), + ) + .toTypedArray() + ) + + @get:Rule val ruleChain: RuleChain = RuleChain.outerRule(assumeRule).around(grantPermissionRule) + + @After + fun tearDown() = runTest { + healthConnectClient.deleteRecords( + MindfulnessSessionRecord::class, + TimeRangeFilter.after(Instant.EPOCH), + ) + } + + // The test aims to verify the wiring with the framework rather than the aggregation logic + // itself. + @Test + fun aggregateRecords() = runTest { + val insertRecordsResponse = + healthConnectClient.insertRecords( + listOf( + MindfulnessSessionRecord( + startTime = START_TIME, + startZoneOffset = ZoneOffset.UTC, + endTime = START_TIME.plusSeconds(150), + endZoneOffset = ZoneOffset.UTC, + metadata = Metadata.manualEntry(), + mindfulnessSessionType = MINDFULNESS_SESSION_TYPE_MEDITATION, + ), + MindfulnessSessionRecord( + startTime = START_TIME.plusSeconds(180), + startZoneOffset = ZoneOffset.UTC, + endTime = START_TIME.plusSeconds(300), + endZoneOffset = ZoneOffset.UTC, + metadata = Metadata.manualEntry(), + mindfulnessSessionType = MINDFULNESS_SESSION_TYPE_BREATHING, + ), + ) + ) + assertThat(insertRecordsResponse.recordIdsList.size).isEqualTo(2) + + val aggregateResponse = + healthConnectClient.aggregate( + AggregateRequest( + setOf(MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL), + TimeRangeFilter.after(Instant.EPOCH), + ) + ) + + with(aggregateResponse) { + assertThat(this[MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL]) + .isEqualTo(Duration.ofSeconds(270)) + } + } +} diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt index 73eb1b793c745..b7360653ee2e9 100644 --- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt +++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt @@ -29,6 +29,7 @@ import android.health.connect.datatypes.FloorsClimbedRecord as PlatformFloorsCli import android.health.connect.datatypes.HeartRateRecord as PlatformHeartRateRecord import android.health.connect.datatypes.HeightRecord as PlatformHeightRecord import android.health.connect.datatypes.HydrationRecord as PlatformHydrationRecord +import android.health.connect.datatypes.MindfulnessSessionRecord as PlatformMindfulnessSessionRecord import android.health.connect.datatypes.NutritionRecord as PlatformNutritionRecord import android.health.connect.datatypes.PowerRecord as PlatformPowerRecord import android.health.connect.datatypes.StepsRecord as PlatformStepsRecord @@ -40,11 +41,10 @@ import android.health.connect.datatypes.units.Length as PlatformLength import android.health.connect.datatypes.units.Mass as PlatformMass import android.health.connect.datatypes.units.Power as PlatformPower import android.health.connect.datatypes.units.Volume as PlatformVolume -import android.os.Build -import android.os.ext.SdkExtensions import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo import androidx.health.connect.client.aggregate.AggregateMetric +import androidx.health.connect.client.impl.platform.records.PlatformActivityIntensityRecord import androidx.health.connect.client.impl.platform.records.PlatformBloodPressureRecord import androidx.health.connect.client.impl.platform.records.PlatformCyclingPedalingCadenceRecord import androidx.health.connect.client.impl.platform.records.PlatformExerciseSessionRecord @@ -56,6 +56,7 @@ import androidx.health.connect.client.impl.platform.records.PlatformSpeedRecord import androidx.health.connect.client.impl.platform.records.PlatformStepsCadenceRecord import androidx.health.connect.client.impl.platform.records.PlatformVelocity import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord +import androidx.health.connect.client.records.ActivityIntensityRecord import androidx.health.connect.client.records.BasalMetabolicRateRecord import androidx.health.connect.client.records.BloodPressureRecord import androidx.health.connect.client.records.CyclingPedalingCadenceRecord @@ -66,6 +67,7 @@ import androidx.health.connect.client.records.FloorsClimbedRecord import androidx.health.connect.client.records.HeartRateRecord import androidx.health.connect.client.records.HeightRecord import androidx.health.connect.client.records.HydrationRecord +import androidx.health.connect.client.records.MindfulnessSessionRecord import androidx.health.connect.client.records.NutritionRecord import androidx.health.connect.client.records.PowerRecord import androidx.health.connect.client.records.RestingHeartRateRecord @@ -77,6 +79,10 @@ import androidx.health.connect.client.records.StepsRecord import androidx.health.connect.client.records.TotalCaloriesBurnedRecord import androidx.health.connect.client.records.WeightRecord import androidx.health.connect.client.records.WheelchairPushesRecord +import androidx.health.connect.client.records.isAtLeastSdkExtension10 +import androidx.health.connect.client.records.isAtLeastSdkExtension13 +import androidx.health.connect.client.records.isAtLeastSdkExtension15 +import androidx.health.connect.client.records.isAtLeastSdkExtension16 import androidx.health.connect.client.units.Energy import androidx.health.connect.client.units.Length import androidx.health.connect.client.units.Mass @@ -88,7 +94,7 @@ import androidx.health.connect.client.units.Volume import java.time.Duration private val DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS = - if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) { + if (isAtLeastSdkExtension10()) { arrayOf( CyclingPedalingCadenceRecord.RPM_AVG to PlatformCyclingPedalingCadenceRecord.RPM_AVG, CyclingPedalingCadenceRecord.RPM_MAX to PlatformCyclingPedalingCadenceRecord.RPM_MAX, @@ -109,12 +115,38 @@ internal val DOUBLE_AGGREGATION_METRIC_TYPE_MAP: *DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS, ) -internal val DURATION_AGGREGATION_METRIC_TYPE_MAP: +internal val DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP: Map, PlatformAggregateMetric> = mapOf( ExerciseSessionRecord.EXERCISE_DURATION_TOTAL to PlatformExerciseSessionRecord.EXERCISE_DURATION_TOTAL, SleepSessionRecord.SLEEP_DURATION_TOTAL to PlatformSleepSessionRecord.SLEEP_DURATION_TOTAL, + *if (isAtLeastSdkExtension15()) { + arrayOf( + MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL to + PlatformMindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL + ) + } else { + emptyArray() + }, + ) + +internal val DURATION_AGGREGATION_METRIC_TYPE_MAP: + Map, PlatformAggregateMetric> = + mapOf( + *if (isAtLeastSdkExtension16()) { + arrayOf( + ActivityIntensityRecord.MODERATE_DURATION_TOTAL to + PlatformActivityIntensityRecord.MODERATE_DURATION_TOTAL, + ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL to + PlatformActivityIntensityRecord.VIGOROUS_DURATION_TOTAL, + ActivityIntensityRecord.DURATION_TOTAL to + PlatformActivityIntensityRecord.DURATION_TOTAL, + ) + as Array, PlatformAggregateMetric>> + } else { + emptyArray() + } ) internal val ENERGY_AGGREGATION_METRIC_TYPE_MAP: @@ -153,6 +185,14 @@ internal val LONG_AGGREGATION_METRIC_TYPE_MAP: StepsRecord.COUNT_TOTAL to PlatformStepsRecord.STEPS_COUNT_TOTAL, WheelchairPushesRecord.COUNT_TOTAL to PlatformWheelchairPushesRecord.WHEEL_CHAIR_PUSHES_COUNT_TOTAL, + *if (isAtLeastSdkExtension16()) { + arrayOf( + ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL to + PlatformActivityIntensityRecord.INTENSITY_MINUTES_TOTAL + ) + } else { + emptyArray() + }, ) internal val GRAMS_AGGREGATION_METRIC_TYPE_MAP: @@ -200,7 +240,7 @@ internal val GRAMS_AGGREGATION_METRIC_TYPE_MAP: NutritionRecord.VITAMIN_E_TOTAL to PlatformNutritionRecord.VITAMIN_E_TOTAL, NutritionRecord.VITAMIN_K_TOTAL to PlatformNutritionRecord.VITAMIN_K_TOTAL, NutritionRecord.ZINC_TOTAL to PlatformNutritionRecord.ZINC_TOTAL, - *if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) { + *if (isAtLeastSdkExtension10()) { arrayOf(NutritionRecord.TRANS_FAT_TOTAL to PlatformNutritionRecord.TRANS_FAT_TOTAL) } else { emptyArray() @@ -225,7 +265,7 @@ internal val POWER_AGGREGATION_METRIC_TYPE_MAP: internal val PRESSURE_AGGREGATION_METRIC_TYPE_MAP: Map, PlatformAggregateMetric> = - if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) { + if (isAtLeastSdkExtension10()) { arrayOf( BloodPressureRecord.DIASTOLIC_AVG to PlatformBloodPressureRecord.DIASTOLIC_AVG, BloodPressureRecord.DIASTOLIC_MAX to PlatformBloodPressureRecord.DIASTOLIC_MAX, @@ -242,7 +282,7 @@ internal val PRESSURE_AGGREGATION_METRIC_TYPE_MAP: @SuppressLint("NewApi") // API 35 is covered by sdk extension check internal val TEMPERATURE_DELTA_METRIC_TYPE_MAP: Map, PlatformAggregateMetric<*>> = - if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13) { + if (isAtLeastSdkExtension13()) { arrayOf( SkinTemperatureRecord.TEMPERATURE_DELTA_AVG to PlatformSkinTemperatureRecord.SKIN_TEMPERATURE_DELTA_AVG, @@ -258,7 +298,7 @@ internal val TEMPERATURE_DELTA_METRIC_TYPE_MAP: internal val VELOCITY_AGGREGATION_METRIC_TYPE_MAP: Map, PlatformAggregateMetric> = - if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) { + if (isAtLeastSdkExtension10()) { arrayOf( SpeedRecord.SPEED_AVG to PlatformSpeedRecord.SPEED_AVG, SpeedRecord.SPEED_MAX to PlatformSpeedRecord.SPEED_MAX, diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt index 0d2edd5ffea21..86db8cb75de8a 100644 --- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt +++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt @@ -32,6 +32,7 @@ import androidx.annotation.RestrictTo import androidx.health.connect.client.aggregate.AggregateMetric import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP +import androidx.health.connect.client.impl.platform.aggregate.DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP @@ -129,6 +130,7 @@ fun AggregateGroupByPeriodRequest.toPlatformRequest(): AggregateRecordsRequest.toAggregationType(): AggregationType { return DOUBLE_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? ?: DURATION_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? + ?: DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? ?: ENERGY_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? ?: GRAMS_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? ?: LENGTH_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType? diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt index 48af0c5c435cc..b5afbdd9a8418 100644 --- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt +++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt @@ -37,6 +37,7 @@ import androidx.health.connect.client.aggregate.AggregationResultGroupedByDurati import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP +import androidx.health.connect.client.impl.platform.aggregate.DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP @@ -58,6 +59,7 @@ import androidx.health.connect.client.impl.platform.records.toSdkDataOrigin import androidx.health.connect.client.impl.platform.request.toAggregationType import androidx.health.connect.client.units.Energy import androidx.health.connect.client.units.Mass +import java.time.Duration import java.time.LocalDateTime import java.time.ZoneOffset @@ -139,11 +141,16 @@ internal fun getLongMetricValues( ): Map { return buildMap { metricValueMap.forEach { (key, value) -> - if ( - key in DURATION_AGGREGATION_METRIC_TYPE_MAP || - key in LONG_AGGREGATION_METRIC_TYPE_MAP - ) { - this[key.metricKey] = value as Long + when (key) { + in LONG_AGGREGATION_METRIC_TYPE_MAP -> { + this[key.metricKey] = value as Long + } + in DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP -> { + this[key.metricKey] = value as Long + } + in DURATION_AGGREGATION_METRIC_TYPE_MAP -> { + this[key.metricKey] = (value as Duration).toMillis() + } } } } diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt index 95ce39591aaa7..d71988374767d 100644 --- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt +++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt @@ -138,10 +138,10 @@ class ActivityIntensityRecord( * [HealthConnectFeatures.FEATURE_ACTIVITY_INTENSITY] as the argument. */ @JvmField - val INTENSITY_MINUTES_TOTAL: AggregateMetric = - AggregateMetric.durationMetric( + val INTENSITY_MINUTES_TOTAL: AggregateMetric = + AggregateMetric.longMetric( "ActivityIntensity", - aggregationType = AggregateMetric.AggregationType.DURATION, + aggregationType = AggregateMetric.AggregationType.TOTAL, fieldName = "intensityMinutes", ) diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt index 972f00399efa4..aaeb330d8afa7 100644 --- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt +++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt @@ -23,14 +23,24 @@ import android.os.ext.SdkExtensions import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo +// We additionally check for SDK_INT check for Robolectric tests, which doesn't fully support +// SdkExtensions. +@RequiresApi(Build.VERSION_CODES.R) +internal fun isAtLeastSdkExtension10(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM || + SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10 +} + @RequiresApi(Build.VERSION_CODES.R) internal fun isAtLeastSdkExtension13(): Boolean { - return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13 + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM || + SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13 } @RequiresApi(Build.VERSION_CODES.R) internal fun isAtLeastSdkExtension15(): Boolean { - return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15 + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA || + SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15 } @RequiresApi(Build.VERSION_CODES.R) diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt new file mode 100644 index 0000000000000..9388db7421550 --- /dev/null +++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2025 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.health.connect.client.impl.platform.aggregate + +import androidx.health.connect.client.aggregate.AggregateMetric +import androidx.health.connect.client.impl.converters.datatype.RECORDS_CLASS_NAME_MAP +import androidx.health.connect.client.records.isAtLeastSdkExtension16 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertWithMessage +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import org.junit.Assume +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(minSdk = 36) +class AggregationMappingsTest { + + private val allAggregationMaps = + listOf( + DOUBLE_AGGREGATION_METRIC_TYPE_MAP, + DURATION_AGGREGATION_METRIC_TYPE_MAP, + DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP, + ENERGY_AGGREGATION_METRIC_TYPE_MAP, + GRAMS_AGGREGATION_METRIC_TYPE_MAP, + KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP, + LENGTH_AGGREGATION_METRIC_TYPE_MAP, + LONG_AGGREGATION_METRIC_TYPE_MAP, + POWER_AGGREGATION_METRIC_TYPE_MAP, + PRESSURE_AGGREGATION_METRIC_TYPE_MAP, + TEMPERATURE_DELTA_METRIC_TYPE_MAP, + VELOCITY_AGGREGATION_METRIC_TYPE_MAP, + VOLUME_AGGREGATION_METRIC_TYPE_MAP, + ) + + @Before + fun setUp() { + // Update this when adding new aggregate metrics. + Assume.assumeTrue(isAtLeastSdkExtension16()) + } + + @Test + fun allAggregateMetrics_areAddedToAggregationMaps() { + val recordClasses = RECORDS_CLASS_NAME_MAP.keys + val aggregateMetrics = + recordClasses.flatMap { recordClass -> + recordClass.java.fields + .filter { it.isAggregateMetric() } + .map { it.get(null) as AggregateMetric<*> } + } + + val allMappedMetrics = allAggregationMaps.flatMap { it.keys } + val missingMetrics = aggregateMetrics.filter { !allMappedMetrics.contains(it) } + val presentMetrics = aggregateMetrics.filter { allMappedMetrics.contains(it) } + assertWithMessage( + "Missing metrics: ${missingMetrics.map { it.metricKey }}, Present Metrics: ${presentMetrics.map { it.metricKey }}" + ) + .that(allMappedMetrics) + .containsAtLeastElementsIn(aggregateMetrics) + } + + private fun Field.isAggregateMetric(): Boolean { + return Modifier.isStatic(modifiers) && + Modifier.isPublic(modifiers) && + AggregateMetric::class.java.isAssignableFrom(type) + } +} diff --git a/libraryversions.toml b/libraryversions.toml index f9297ddb432a3..c2edd0ed55d14 100644 --- a/libraryversions.toml +++ b/libraryversions.toml @@ -160,7 +160,7 @@ VECTORDRAWABLE_SEEKABLE = "1.0.0" VERSIONED_PARCELABLE = "1.2.0-rc01" VIEWPAGER = "1.1.0-rc01" VIEWPAGER2 = "1.2.0-alpha01" -WEAR = "1.4.0-beta01" +WEAR = "1.4.0-rc01" WEAR_COMPOSE = "1.6.0-alpha08" WEAR_CORE = "1.0.0-rc01" WEAR_INPUT = "1.2.0-rc01" @@ -168,11 +168,11 @@ WEAR_INPUT_TESTING = "1.2.0-rc01" WEAR_ONGOING = "1.1.0-rc01" WEAR_PHONE_INTERACTIONS = "1.1.0-rc01" WEAR_PROTOLAYOUT = "1.4.0-alpha04" -WEAR_REMOTE_INTERACTIONS = "1.2.0-beta01" +WEAR_REMOTE_INTERACTIONS = "1.2.0-rc01" WEAR_TILES = "1.6.0-alpha04" WEAR_TOOLING_PREVIEW = "1.0.0-rc01" -WEAR_WATCHFACE = "1.3.0-beta01" -WEAR_WATCHFACEPUSH = "1.0.0-beta01" +WEAR_WATCHFACE = "1.3.0-rc01" +WEAR_WATCHFACEPUSH = "1.0.0-rc01" WEBGPU = "1.0.0-alpha03" WEBKIT = "1.16.0-alpha01" # Adding a comment to prevent merge conflicts for Window artifact diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java index 8615f5bc5660f..27027dd4d355d 100644 --- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java +++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java @@ -263,6 +263,7 @@ public void testFledgeAuctionSelectionFlow_overall_Success() throws Exception { } @Test + @Ignore("b/463382540") public void testAdSelection_etldViolation_failure() throws Exception { // Skip the test if the right SDK extension is not present. Assume.assumeTrue( @@ -442,6 +443,7 @@ public void testAdSelection_skipAdsMalformedBiddingLogic_success() throws Except } @Test + @Ignore("b/463382540") public void testAdSelection_malformedScoringLogic_failure() throws Exception { // Skip the test if the right SDK extension is not present. Assume.assumeTrue( @@ -550,6 +552,7 @@ public void testAdSelection_skipAdsFailedGettingBiddingLogic_success() throws Ex } @Test + @Ignore("b/463382540") public void testAdSelection_errorGettingScoringLogic_failure() throws Exception { // Skip the test if the right SDK extension is not present. Assume.assumeTrue( diff --git a/settings.gradle b/settings.gradle index 28d2acbb74523..2f2b448cab994 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1234,10 +1234,11 @@ includeProject(":xr:compose:compose-testing", [BuildType.XR]) includeProject(":xr:compose:integration-tests:testapp", [BuildType.XR]) includeProject(":xr:compose:material3:material3", [BuildType.XR]) includeProject(":xr:compose:material3:integration-tests:testapp", [BuildType.XR]) -includeProject(":xr:glimmer:glimmer", [BuildType.COMPOSE]) -includeProject(":xr:glimmer:benchmark", [BuildType.COMPOSE]) -includeProject(":xr:glimmer:glimmer:glimmer-samples", "xr/glimmer/glimmer/samples", [BuildType.COMPOSE]) -includeProject(":xr:glimmer:integration-tests:demos", [BuildType.COMPOSE]) +includeProject(":xr:glimmer:glimmer", [BuildType.XR, BuildType.COMPOSE]) +includeProject(":xr:glimmer:benchmark", [BuildType.XR, BuildType.COMPOSE]) +includeProject(":xr:glimmer:glimmer:glimmer-samples", "xr/glimmer/glimmer/samples", [BuildType.XR, BuildType.COMPOSE]) +includeProject(":xr:glimmer:integration-tests:demos", [BuildType.XR, BuildType.COMPOSE]) +includeProject(":xr:glimmer:test-utils", [BuildType.XR, BuildType.COMPOSE]) includeProject(":xr:projected:projected", [BuildType.XR]) includeProject(":xr:projected:integration-tests:testapp", [BuildType.XR]) includeProject(":xr:projected:projected-binding", [BuildType.XR]) diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt index aa203b032b021..0126476e1db74 100644 --- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt +++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt @@ -21,7 +21,9 @@ import android.graphics.Canvas import android.graphics.ColorFilter import android.graphics.PixelFormat import android.graphics.drawable.Drawable +import android.os.SystemClock import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.View.MeasureSpec import android.view.View.MeasureSpec.EXACTLY @@ -227,6 +229,57 @@ class SlidingPaneLayoutTest { } } + @Test + fun testCoerceInWithLargeMinOnMotionEvent_doNotCrash() { + val context = InstrumentationRegistry.getInstrumentation().context + val firstChild = View(context).apply { minimumWidth = 500 } + val secondChild = View(context) + val spl = + SlidingPaneLayout(context).apply { + isOverlappingEnabled = false + isUserResizingEnabled = true + addView(firstChild, SlidingPaneLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT)) + addView(secondChild, SlidingPaneLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)) + } + + // Measure and layout with a width smaller than the minimum width of the first child. + spl.measureAndLayout(300, 300) + + // Inject motion events to trigger divider dragging logic. + val downTime = SystemClock.uptimeMillis() + val eventTime = SystemClock.uptimeMillis() + val dividerX = spl.visualDividerPosition.toFloat() + val dividerY = 150f + + val downEvent = + MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, dividerX, dividerY, 0) + spl.dispatchTouchEvent(downEvent) + + val moveEvent = + MotionEvent.obtain( + downTime, + eventTime + 10, + MotionEvent.ACTION_MOVE, + dividerX + 10, + dividerY, + 0, + ) + // Previously, this would crash because clampDraggingDividerPosition would call coerceIn + // with min > max. + spl.dispatchTouchEvent(moveEvent) + + val upEvent = + MotionEvent.obtain( + downTime, + eventTime + 20, + MotionEvent.ACTION_UP, + dividerX + 10, + dividerY, + 0, + ) + spl.dispatchTouchEvent(upEvent) + } + @Test fun userResizingConfiguration() { val context = InstrumentationRegistry.getInstrumentation().context diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt index 297b9a9dcf396..22828e1941fd0 100644 --- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt +++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt @@ -2729,11 +2729,11 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : if (isLayoutRtl) { val startBound = (width - (paddingRight + lp.rightMargin + slideableView.width)) val endBound = startBound - slideRange - newLeft.coerceIn(endBound, startBound) + newLeft.coerceAtMost(startBound).coerceAtLeast(endBound) } else { val startBound = paddingLeft + lp.leftMargin val endBound = startBound + slideRange - newLeft.coerceIn(startBound, endBound) + newLeft.coerceAtMost(endBound).coerceAtLeast(startBound) } return newLeft } @@ -2880,15 +2880,18 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : leftChild = getChildAt(0) rightChild = getChildAt(1) } - return proposedPositionX.coerceIn( - paddingLeft + - leftChild.spLayoutParams.horizontalMargin + - getMinimumChildWidth(leftChild), - width - - paddingRight - - rightChild.spLayoutParams.horizontalMargin - - getMinimumChildWidth(rightChild), - ) + return proposedPositionX + .coerceAtMost( + width - + paddingRight - + rightChild.spLayoutParams.horizontalMargin - + getMinimumChildWidth(rightChild) + ) + .coerceAtLeast( + paddingLeft + + leftChild.spLayoutParams.horizontalMargin + + getMinimumChildWidth(leftChild) + ) } override fun onUserResizeStarted() { diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt index f0dee44deb9d7..0c1775a8e4ab0 100644 --- a/wear/compose/compose-foundation/api/current.txt +++ b/wear/compose/compose-foundation/api/current.txt @@ -969,10 +969,10 @@ package androidx.wear.compose.foundation.rotary { method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior! snapBehavior-uFdPcIQ(androidx.wear.compose.foundation.lazy.ScalingLazyListState!, float, boolean, androidx.compose.runtime.Composer!, int, int); method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior! snapBehavior-uFdPcIQ(androidx.wear.compose.foundation.pager.PagerState!, float, boolean, androidx.compose.runtime.Composer!, int, int); property public static float HighSnapSensitivity; - property public static float SnapSensitivity; + property public static float LowSnapSensitivity; field public static final float HighSnapSensitivity = 0.8f; field public static final androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults INSTANCE; - field public static final float SnapSensitivity = 0.4f; + field public static final float LowSnapSensitivity = 0.4f; } public final class RotaryScrollableKt { diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt index f0dee44deb9d7..0c1775a8e4ab0 100644 --- a/wear/compose/compose-foundation/api/restricted_current.txt +++ b/wear/compose/compose-foundation/api/restricted_current.txt @@ -969,10 +969,10 @@ package androidx.wear.compose.foundation.rotary { method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior! snapBehavior-uFdPcIQ(androidx.wear.compose.foundation.lazy.ScalingLazyListState!, float, boolean, androidx.compose.runtime.Composer!, int, int); method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior! snapBehavior-uFdPcIQ(androidx.wear.compose.foundation.pager.PagerState!, float, boolean, androidx.compose.runtime.Composer!, int, int); property public static float HighSnapSensitivity; - property public static float SnapSensitivity; + property public static float LowSnapSensitivity; field public static final float HighSnapSensitivity = 0.8f; field public static final androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults INSTANCE; - field public static final float SnapSensitivity = 0.4f; + field public static final float LowSnapSensitivity = 0.4f; } public final class RotaryScrollableKt { diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt index 116aaaf22e8af..6c04cebb6e7de 100644 --- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt +++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt @@ -904,9 +904,9 @@ class RotaryScrollTest { @Test fun rotaryScrollable_snap_sensitivity_avoids_divide_by_zero() { - val defaultSensitivity = RotaryScrollableDefaults.SnapSensitivity + val lowSensitivity = RotaryScrollableDefaults.LowSnapSensitivity val highSensitivity = RotaryScrollableDefaults.HighSnapSensitivity - val sensitivityRange = highSensitivity - defaultSensitivity + val sensitivityRange = highSensitivity - lowSensitivity val defaultSensitivityValues = RotarySnapSensitivityValues.Default val highSensitivityValues = RotarySnapSensitivityValues.High @@ -927,9 +927,9 @@ class RotaryScrollTest { @Test fun rotaryScrollable_snap_sensitivity_extrapolates_lower() { - val defaultSensitivity = RotaryScrollableDefaults.SnapSensitivity + val lowSensitivity = RotaryScrollableDefaults.LowSnapSensitivity - val values = RotarySnapSensitivityValues(defaultSensitivity * 0.9f) + val values = RotarySnapSensitivityValues(lowSensitivity * 0.9f) assertThat(values.minThresholdDivider) .isLessThan(RotarySnapSensitivityValues.Default.minThresholdDivider) diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt index 2bb475d99fc4a..a77b6ffe46e2c 100644 --- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt +++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt @@ -263,13 +263,13 @@ public interface RotarySnapLayoutInfoProvider { public object RotaryScrollableDefaults { /** - * Default snap sensitivity: The standard setting, intended for general use when the user is + * Low snap sensitivity: the standard setting, intended for general use when the user is * performing typical UI navigation. */ - public const val SnapSensitivity: Float = 0.4f + public const val LowSnapSensitivity: Float = 0.4f /** - * High snap sensitivity. Recommended for contexts where even a light or minimal gesture should + * High snap sensitivity: recommended for contexts where even a light or minimal gesture should * trigger movement, such as navigating a long list (e.g. at least 10 items) where quick * scrolling is desired. */ @@ -322,7 +322,7 @@ public object RotaryScrollableDefaults { * scrolling (true by default). It's recommended to keep the default value of true for premium * scrolling experience. * @param snapSensitivity Configures the sensitivity for rotary snapping. Defaults to - * [RotaryScrollableDefaults.SnapSensitivity]. + * [RotaryScrollableDefaults.LowSnapSensitivity]. */ @Composable public fun snapBehavior( @@ -330,7 +330,7 @@ public object RotaryScrollableDefaults { layoutInfoProvider: RotarySnapLayoutInfoProvider, snapOffset: Dp = 0.dp, hapticFeedbackEnabled: Boolean = true, - @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = SnapSensitivity, + @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = LowSnapSensitivity, ): RotaryScrollableBehavior = snapBehavior( scrollableState = scrollableState, @@ -384,14 +384,14 @@ public object RotaryScrollableDefaults { * scrolling (true by default). It's recommended to keep the default value of true for premium * scrolling experience. * @param snapSensitivity Configures the sensitivity for rotary snapping. Defaults to - * [RotaryScrollableDefaults.SnapSensitivity]. + * [RotaryScrollableDefaults.LowSnapSensitivity]. */ @Composable public fun snapBehavior( scrollableState: ScalingLazyListState, snapOffset: Dp = 0.dp, hapticFeedbackEnabled: Boolean = true, - @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = SnapSensitivity, + @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = LowSnapSensitivity, ): RotaryScrollableBehavior = snapBehavior( scrollableState = scrollableState, @@ -451,14 +451,14 @@ public object RotaryScrollableDefaults { * scrolling (true by default). It's recommended to keep the default value of true for premium * scrolling experience. * @param snapSensitivity Configures the sensitivity for rotary snapping. Defaults to - * [SnapSensitivity]. + * [LowSnapSensitivity]. */ @Composable public fun snapBehavior( scrollableState: TransformingLazyColumnState, snapOffset: Dp = 0.dp, hapticFeedbackEnabled: Boolean = true, - @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = SnapSensitivity, + @FloatRange(from = 0.0, to = 1.0) snapSensitivity: Float = LowSnapSensitivity, ): RotaryScrollableBehavior = snapBehavior( scrollableState = scrollableState, @@ -483,8 +483,8 @@ public object RotaryScrollableDefaults { * scrolling experience. * @param snapSensitivity Configures the sensitivity for rotary snapping. Defaults to * [RotaryScrollableDefaults.HighSnapSensitivity] which is suitable for Pagers with at least - * 10 pages. See also [RotaryScrollableDefaults.SnapSensitivity] for context where there are - * fewer pages. + * 10 pages. See also [RotaryScrollableDefaults.LowSnapSensitivity] for context where there + * are fewer pages. */ @Composable public fun snapBehavior( @@ -1826,9 +1826,9 @@ internal constructor( internal fun RotarySnapSensitivityValues(sensitivity: Float): RotarySnapSensitivityValues { // Calculate fraction of this sensitivity value, with reference to the two recommended values. val fraction = - (sensitivity - RotaryScrollableDefaults.SnapSensitivity) / + (sensitivity - RotaryScrollableDefaults.LowSnapSensitivity) / (RotaryScrollableDefaults.HighSnapSensitivity - - RotaryScrollableDefaults.SnapSensitivity) + RotaryScrollableDefaults.LowSnapSensitivity) val defaultValues = RotarySnapSensitivityValues.Default val highValues = RotarySnapSensitivityValues.High diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt index 732aa558ae300..495b9301d49a8 100644 --- a/wear/compose/compose-material3/api/current.txt +++ b/wear/compose/compose-material3/api/current.txt @@ -1091,12 +1091,12 @@ package androidx.wear.compose.material3 { public final class PagerScaffoldDefaults { method @InaccessibleFromKotlin public androidx.compose.animation.core.AnimationSpec getFadeOutAnimationSpec(); method @InaccessibleFromKotlin public float getHighSnapPositionalThreshold(); - method @InaccessibleFromKotlin public float getSnapPositionalThreshold(); + method @InaccessibleFromKotlin public float getLowSnapPositionalThreshold(); method @KotlinOnly @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior snapWithSpringFlingBehavior(androidx.wear.compose.foundation.pager.PagerState state); method @BytecodeOnly @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior snapWithSpringFlingBehavior(androidx.wear.compose.foundation.pager.PagerState, androidx.compose.runtime.Composer?, int); property public androidx.compose.animation.core.AnimationSpec FadeOutAnimationSpec; property public float HighSnapPositionalThreshold; - property public float SnapPositionalThreshold; + property public float LowSnapPositionalThreshold; field public static final androidx.wear.compose.material3.PagerScaffoldDefaults INSTANCE; } diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt index 732aa558ae300..495b9301d49a8 100644 --- a/wear/compose/compose-material3/api/restricted_current.txt +++ b/wear/compose/compose-material3/api/restricted_current.txt @@ -1091,12 +1091,12 @@ package androidx.wear.compose.material3 { public final class PagerScaffoldDefaults { method @InaccessibleFromKotlin public androidx.compose.animation.core.AnimationSpec getFadeOutAnimationSpec(); method @InaccessibleFromKotlin public float getHighSnapPositionalThreshold(); - method @InaccessibleFromKotlin public float getSnapPositionalThreshold(); + method @InaccessibleFromKotlin public float getLowSnapPositionalThreshold(); method @KotlinOnly @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior snapWithSpringFlingBehavior(androidx.wear.compose.foundation.pager.PagerState state); method @BytecodeOnly @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior snapWithSpringFlingBehavior(androidx.wear.compose.foundation.pager.PagerState, androidx.compose.runtime.Composer?, int); property public androidx.compose.animation.core.AnimationSpec FadeOutAnimationSpec; property public float HighSnapPositionalThreshold; - property public float SnapPositionalThreshold; + property public float LowSnapPositionalThreshold; field public static final androidx.wear.compose.material3.PagerScaffoldDefaults INSTANCE; } diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PagerScaffoldSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PagerScaffoldSample.kt index 534c25cb67891..98643a01b5fd3 100644 --- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PagerScaffoldSample.kt +++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PagerScaffoldSample.kt @@ -94,13 +94,13 @@ fun HorizontalPagerScaffoldWithLowSensitivitySample(navigateBack: () -> Unit) { PagerDefaults.snapFlingBehavior( state = pagerState, maxFlingPages = 0, - snapPositionalThreshold = PagerScaffoldDefaults.SnapPositionalThreshold, + snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, snapAnimationSpec = PagerDefaults.SnapAnimationSpec, ), rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior( pagerState = pagerState, - snapSensitivity = RotaryScrollableDefaults.SnapSensitivity, + snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity, ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { @@ -174,13 +174,13 @@ fun VerticalPagerScaffoldWithLowSensitivitySample() { PagerDefaults.snapFlingBehavior( state = pagerState, maxFlingPages = 0, - snapPositionalThreshold = PagerScaffoldDefaults.SnapPositionalThreshold, + snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, snapAnimationSpec = PagerDefaults.SnapAnimationSpec, ), rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior( pagerState = pagerState, - snapSensitivity = RotaryScrollableDefaults.SnapSensitivity, + snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity, ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PagerScaffold.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PagerScaffold.kt index 27aad6cfa8e05..f986491f7ccef 100644 --- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PagerScaffold.kt +++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PagerScaffold.kt @@ -253,7 +253,7 @@ public object PagerScaffoldDefaults { * * @sample androidx.wear.compose.material3.samples.HorizontalPagerScaffoldWithLowSensitivitySample */ - public val SnapPositionalThreshold: Float = 0.1f + public val LowSnapPositionalThreshold: Float = 0.1f /** * Recommended fling behavior for pagers on Wear when using Material3, snaps at most one page at @@ -276,7 +276,7 @@ public object PagerScaffoldDefaults { state = state, maxFlingPages = 1, snapAnimationSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), - snapPositionalThreshold = 0.35f, + snapPositionalThreshold = HighSnapPositionalThreshold, ) } diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonGroupPreview.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonGroupPreview.kt index f55f0841a0877..73093bf6cb42f 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonGroupPreview.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonGroupPreview.kt @@ -20,6 +20,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.MailOutline +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -31,6 +32,7 @@ import androidx.compose.remote.creation.compose.modifier.size import androidx.compose.remote.creation.compose.modifier.widthIn import androidx.compose.remote.creation.compose.state.rb import androidx.compose.remote.creation.compose.state.rf +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.creation.profile.Profile import androidx.compose.remote.tooling.preview.RemotePreview import androidx.compose.runtime.Composable @@ -83,6 +85,7 @@ private fun RemoteButtonGroupTwoButtonsPreview( @RemoteComposable private fun Button(imageVector: ImageVector, modifier: RemoteModifier) { RemoteIconButton( + testAction, modifier = modifier.widthIn(RemoteButtonGroupDefaults.MinWidth), enabled = true.rb, colors = tonalColors, @@ -127,3 +130,5 @@ private val tonalColors RemoteMaterialTheme.colorScheme.primary.copy(alpha = 0.12f.rf), disabledContentColor = RemoteMaterialTheme.colorScheme.primary.copy(0.38f.rf), ) + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonPreview.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonPreview.kt index 85580dd8ed3e1..25b1ebb836480 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonPreview.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteButtonPreview.kt @@ -17,6 +17,7 @@ package androidx.wear.compose.remote.material3.previews +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -28,6 +29,7 @@ import androidx.compose.remote.creation.compose.state.RemoteColor import androidx.compose.remote.creation.compose.state.RemoteString import androidx.compose.remote.creation.compose.state.rb import androidx.compose.remote.creation.compose.state.rdp +import androidx.compose.remote.creation.compose.state.rf import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.creation.profile.Profile import androidx.compose.remote.tooling.preview.RemotePreview @@ -46,6 +48,7 @@ import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices @RemoteComposable fun RemoteButtonEnabled() { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), enabled = true.rb, content = { RemoteText("button_enabled".rs) }, @@ -62,6 +65,7 @@ private fun RemoteButtonEnabledPreview( @RemoteComposable fun RemoteButtonWithBorder() { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), border = 8.rdp, borderColor = RemoteColor(Color.Green), @@ -80,6 +84,7 @@ private fun RemoteButtonWithBorderPreview( @RemoteComposable fun RemoteButtonWithSecondaryLabel() { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), secondaryLabel = { RemoteText(RemoteString("secondaryLabel")) }, label = { RemoteText(RemoteString("label")) }, @@ -90,6 +95,7 @@ fun RemoteButtonWithSecondaryLabel() { @RemoteComposable fun RemoteButtonWithIcon() { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), icon = { RemoteIcon( @@ -112,6 +118,7 @@ private fun RemoteButtonWithIconPreview( @RemoteComposable fun RemoteButtonWithIconAndSecondaryLabel() { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), icon = { RemoteIcon( @@ -135,6 +142,7 @@ private fun RemoteButtonWithIconAndSecondaryLabelPreview( @RemoteComposable fun RemoteCompactButtonWithIcon() { RemoteCompactButton( + onClick = testAction, modifier = RemoteModifier, icon = { RemoteIcon( @@ -157,7 +165,11 @@ private fun RemoteCompactButtonWithIconPreview( @Composable @RemoteComposable fun RemoteCompactButtonWithLabel() { - RemoteCompactButton(modifier = RemoteModifier, label = { RemoteText("label".rs) }) + RemoteCompactButton( + onClick = testAction, + modifier = RemoteModifier, + label = { RemoteText("label".rs) }, + ) } @WearPreviewDevices @@ -170,6 +182,7 @@ private fun RemoteCompactButtonWithLabelPreview( @RemoteComposable fun RemoteCompactButtonWithIconAndLabel() { RemoteCompactButton( + onClick = testAction, modifier = RemoteModifier, icon = { RemoteIcon( @@ -202,3 +215,5 @@ private fun Container( content = content, ) } + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteIconButtonPreview.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteIconButtonPreview.kt index b63bdf5f3e41b..2364584e594af 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteIconButtonPreview.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteIconButtonPreview.kt @@ -17,6 +17,7 @@ package androidx.wear.compose.remote.material3.previews +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -28,6 +29,7 @@ import androidx.compose.remote.creation.compose.state.RemoteColor import androidx.compose.remote.creation.compose.state.rb import androidx.compose.remote.creation.compose.state.rdp import androidx.compose.remote.creation.compose.state.rf +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.creation.profile.Profile import androidx.compose.remote.tooling.preview.RemotePreview import androidx.compose.runtime.Composable @@ -42,7 +44,7 @@ import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices @Composable @RemoteComposable fun RemoteIconButtonEnabled() { - RemoteIconButton(enabled = true.rb) { + RemoteIconButton(testAction, enabled = true.rb) { RemoteIcon(imageVector = TestImageVectors.VolumeUp, contentDescription = null) } } @@ -56,7 +58,7 @@ private fun RemoteIconButtonEnabledPreview( @Composable @RemoteComposable fun RemoteIconButtonTonal() { - RemoteIconButton(enabled = true.rb, colors = tonalColors) { + RemoteIconButton(testAction, enabled = true.rb, colors = tonalColors) { RemoteIcon( modifier = RemoteModifier.size(RemoteIconButtonDefaults.SmallIconSize), imageVector = TestImageVectors.VolumeUp, @@ -75,6 +77,7 @@ private fun RemoteIconButtonTonalPreview( @RemoteComposable fun RemoteIconButtonOutlined() { RemoteIconButton( + testAction, border = 1.rdp, borderColor = RemoteMaterialTheme.colorScheme.outline, enabled = true.rb, @@ -130,3 +133,5 @@ private fun Container( content = content, ) } + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteTextButtonPreview.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteTextButtonPreview.kt index 2fb0bd980c324..47543826ce1b1 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteTextButtonPreview.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/previews/RemoteTextButtonPreview.kt @@ -16,6 +16,7 @@ package androidx.wear.compose.remote.material3.previews +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -41,7 +42,7 @@ import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices @Composable @RemoteComposable fun RemoteTextButtonEnabled() { - RemoteTextButton(enabled = true.rb) { RemoteText("ABC".rs) } + RemoteTextButton(testAction, enabled = true.rb) { RemoteText("ABC".rs) } } @WearPreviewDevices @@ -53,7 +54,9 @@ private fun RemoteTextButtonEnabledPreview( @Composable @RemoteComposable fun RemoteTextButtonTonal() { - RemoteTextButton(enabled = true.rb, colors = filledTonalColor()) { RemoteText("ABC".rs) } + RemoteTextButton(testAction, enabled = true.rb, colors = filledTonalColor()) { + RemoteText("ABC".rs) + } } @WearPreviewDevices @@ -66,6 +69,7 @@ private fun RemoteTextButtonTonalPreview( @RemoteComposable fun RemoteTextButtonOutline() { RemoteTextButton( + testAction, border = 1.rdp, borderColor = RemoteMaterialTheme.colorScheme.outline, enabled = true.rb, @@ -114,3 +118,5 @@ private fun Container( content = content, ) } + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteButtonGroupSample.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteButtonGroupSample.kt index 3b33174a1a63e..1ab1d3e40162b 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteButtonGroupSample.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteButtonGroupSample.kt @@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.MailOutline +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -32,6 +33,7 @@ import androidx.compose.remote.creation.compose.modifier.size import androidx.compose.remote.creation.compose.modifier.widthIn import androidx.compose.remote.creation.compose.state.rb import androidx.compose.remote.creation.compose.state.rf +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.tooling.preview.RemotePreview import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector @@ -72,6 +74,7 @@ private fun Spacer() { @RemoteComposable private fun Button(imageVector: ImageVector, modifier: RemoteModifier) { RemoteIconButton( + testAction, modifier = modifier.widthIn(RemoteButtonGroupDefaults.MinWidth), enabled = true.rb, colors = tonalColors, @@ -106,3 +109,5 @@ private val tonalColors RemoteMaterialTheme.colorScheme.primary.copy(alpha = 0.12f.rf), disabledContentColor = RemoteMaterialTheme.colorScheme.primary.copy(0.38f.rf), ) + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteIconButtonSample.kt b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteIconButtonSample.kt index 8c1cc21bbcbf5..2cf8e6e7bc6e9 100644 --- a/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteIconButtonSample.kt +++ b/wear/compose/remote/remote-material3/samples/src/main/java/androidx/wear/compose/remote/material3/samples/RemoteIconButtonSample.kt @@ -17,6 +17,7 @@ package androidx.wear.compose.remote.material3.samples import androidx.annotation.Sampled +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -24,6 +25,7 @@ import androidx.compose.remote.creation.compose.layout.RemoteComposable import androidx.compose.remote.creation.compose.modifier.RemoteModifier import androidx.compose.remote.creation.compose.modifier.fillMaxSize import androidx.compose.remote.creation.compose.state.rf +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.tooling.preview.RemotePreview import androidx.compose.runtime.Composable import androidx.wear.compose.remote.material3.RemoteIcon @@ -36,7 +38,7 @@ import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices @Sampled @Composable fun RemoteIconButtonSimpleSample(modifier: RemoteModifier = RemoteModifier) { - RemoteIconButton(modifier = modifier, colors = tonalColors) { + RemoteIconButton(testAction, modifier = modifier, colors = tonalColors) { RemoteIcon(imageVector = TestImageVectors.VolumeUp, contentDescription = null) } } @@ -72,3 +74,5 @@ private val tonalColors RemoteMaterialTheme.colorScheme.primary.copy(alpha = 0.12f.rf), disabledContentColor = RemoteMaterialTheme.colorScheme.primary.copy(0.38f.rf), ) + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteButtonTest.kt b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteButtonTest.kt index 5234b4d284671..a8d6203694432 100644 --- a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteButtonTest.kt +++ b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteButtonTest.kt @@ -49,8 +49,8 @@ import androidx.wear.compose.remote.material3.previews.RemoteButtonWithBorder import androidx.wear.compose.remote.material3.previews.RemoteButtonWithIcon import androidx.wear.compose.remote.material3.previews.RemoteButtonWithIconAndSecondaryLabel import androidx.wear.compose.remote.material3.previews.RemoteButtonWithSecondaryLabel +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -89,7 +89,11 @@ class RemoteButtonTest { creationDisplayInfo = creationDisplayInfo, ) { Center(RemoteModifier.fillMaxSize()) { - RemoteButton(modifier = RemoteModifier.buttonSizeModifier(), enabled = false.rb) { + RemoteButton( + onClick = testAction, + modifier = RemoteModifier.buttonSizeModifier(), + enabled = false.rb, + ) { RemoteText("button_disabled".rs) } } @@ -114,7 +118,11 @@ class RemoteButtonTest { disabledIconColor = RemoteColor(Color.Black), ) Center(RemoteModifier.fillMaxSize()) { - RemoteButton(modifier = RemoteModifier.buttonSizeModifier(), colors = colors) { + RemoteButton( + onClick = testAction, + modifier = RemoteModifier.buttonSizeModifier(), + colors = colors, + ) { RemoteText("button_overrides_colors".rs) } } @@ -129,6 +137,7 @@ class RemoteButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), contentPadding = RemotePaddingValues(50.rdp), ) { @@ -146,6 +155,7 @@ class RemoteButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteButton( + onClick = testAction, modifier = RemoteModifier.size(180.rdp, 100.rdp), contentPadding = RemotePaddingValues(0.rdp), ) { @@ -163,6 +173,7 @@ class RemoteButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), contentPadding = RemotePaddingValues(0.rdp), ) { @@ -195,6 +206,7 @@ class RemoteButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteButton( + onClick = testAction, modifier = RemoteModifier.size(150.rdp), border = 8.rdp, borderColor = RemoteColor(Color.Green), @@ -218,6 +230,7 @@ class RemoteButtonTest { val containerPainter = RemoteButtonDefaults.containerPainter(painterRemoteBitmap(backgroundImage)) RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), containerPainter = containerPainter, ) { @@ -242,6 +255,7 @@ class RemoteButtonTest { val containerPainter = RemoteButtonDefaults.containerPainter(painterRemoteBitmap(backgroundImage)) RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), enabled = enabled, containerPainter = containerPainter, @@ -284,28 +298,12 @@ class RemoteButtonTest { @Test fun button_enabled_and_has_action_click_modifier_is_added() { - val expectedContent = - """ -DATA_TEXT<43> = "button_enabled" -ROOT [-2:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE - ROW [-3:-1] = [0.0, 0.0, 73.5, 31.5] VISIBLE - MODIFIERS - HEIGHT_IN = [52.0, 3.4028235E38] - WIDTH_IN = [12.0, 3.4028235E38] - DRAW_CONTENT - CLICK_MODIFIER - HOST_NAMED_ACTION = 45 : 42 - SEMANTICS = SEMANTICS BUTTON - PADDING = [36.75, 15.75, 36.75, 15.75] - TEXT_LAYOUT [-5:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE (43:"null") - MODIFIERS""" - .trimIndent() runBlocking { val document = remoteComposeTestRule.captureDocument(context = context) { RemoteButton( modifier = RemoteModifier.buttonSizeModifier(), - onClick = arrayOf(HostAction("TestAction".rs, 0f.rf)), + onClick = testAction, enabled = true.rb, ) { RemoteText("button_enabled".rs) @@ -313,31 +311,17 @@ ROOT [-2:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE } val actualContent = document.displayHierarchy() - assertEquals(expectedContent.normalizeWhiteSpace(), actualContent.normalizeWhiteSpace()) + assertThat(actualContent.normalizeWhiteSpace()).contains("CLICK_MODIFIER") } } @Test fun button_disabled_click_modifier_is_not_added() { - val expectedContent = - """ -DATA_TEXT<42> = "button_disabled" -ROOT [-2:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE - ROW [-3:-1] = [0.0, 0.0, 73.5, 31.5] VISIBLE - MODIFIERS - HEIGHT_IN = [52.0, 3.4028235E38] - WIDTH_IN = [12.0, 3.4028235E38] - DRAW_CONTENT - SEMANTICS = SEMANTICS BUTTON disabled - PADDING = [36.75, 15.75, 36.75, 15.75] - TEXT_LAYOUT [-5:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE (42:"null") - MODIFIERS""" - .trimIndent() - runBlocking { val document = remoteComposeTestRule.captureDocument(context = context) { RemoteButton( + onClick = testAction, modifier = RemoteModifier.buttonSizeModifier(), enabled = false.rb, ) { @@ -346,7 +330,7 @@ ROOT [-2:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE } val actualContent = document.displayHierarchy() - assertEquals(expectedContent.normalizeWhiteSpace(), actualContent.normalizeWhiteSpace()) + assertThat(actualContent.normalizeWhiteSpace()).doesNotContain("CLICK_MODIFIER") } } @@ -367,4 +351,6 @@ ROOT [-2:-1] = [0.0, 0.0, 0.0, 0.0] VISIBLE content = content, ) } + + private val testAction = HostAction("testAction".rs, 1.rf) } diff --git a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteCompactButtonTest.kt b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteCompactButtonTest.kt index f2e157e78c47f..0022afbc52cc2 100644 --- a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteCompactButtonTest.kt +++ b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteCompactButtonTest.kt @@ -19,6 +19,7 @@ package androidx.wear.compose.remote.material3 import android.annotation.SuppressLint import android.content.Context import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -26,6 +27,7 @@ import androidx.compose.remote.creation.compose.layout.RemoteComposable import androidx.compose.remote.creation.compose.modifier.RemoteModifier import androidx.compose.remote.creation.compose.modifier.fillMaxSize import androidx.compose.remote.creation.compose.state.rb +import androidx.compose.remote.creation.compose.state.rf import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.player.compose.test.utils.screenshot.TargetPlayer import androidx.compose.remote.player.compose.test.utils.screenshot.rule.RemoteComposeScreenshotTestRule @@ -66,6 +68,7 @@ class RemoteCompactButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteCompactButton( + onClick = testAction, modifier = RemoteModifier, enabled = false.rb, label = { RemoteText("disabled".rs) }, @@ -118,3 +121,5 @@ class RemoteCompactButtonTest { ) } } + +private val testAction = HostAction("".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteIconButtonTest.kt b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteIconButtonTest.kt index 9f92330e95f1c..8c3d4d6a7615f 100644 --- a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteIconButtonTest.kt +++ b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteIconButtonTest.kt @@ -18,6 +18,7 @@ package androidx.wear.compose.remote.material3 import android.content.Context import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -29,6 +30,7 @@ import androidx.compose.remote.creation.compose.state.RemoteBoolean import androidx.compose.remote.creation.compose.state.RemoteColor import androidx.compose.remote.creation.compose.state.rdp import androidx.compose.remote.creation.compose.state.rf +import androidx.compose.remote.creation.compose.state.rs import androidx.compose.remote.player.compose.test.utils.screenshot.TargetPlayer import androidx.compose.remote.player.compose.test.utils.screenshot.rule.RemoteComposeScreenshotTestRule import androidx.compose.runtime.Composable @@ -76,7 +78,7 @@ class RemoteIconButtonTest { creationDisplayInfo = creationDisplayInfo, ) { Center(RemoteModifier.fillMaxSize()) { - RemoteIconButton(enabled = RemoteBoolean(false)) { + RemoteIconButton(testAction, enabled = RemoteBoolean(false)) { RemoteIcon(imageVector = TestImageVectors.VolumeUp, contentDescription = null) } } @@ -100,7 +102,11 @@ class RemoteIconButtonTest { creationDisplayInfo = creationDisplayInfo, ) { Center(RemoteModifier.fillMaxSize()) { - RemoteIconButton(enabled = RemoteBoolean(false), colors = FILLED_TONAL_COLOR) { + RemoteIconButton( + testAction, + enabled = RemoteBoolean(false), + colors = FILLED_TONAL_COLOR, + ) { RemoteIcon( modifier = RemoteModifier.size(RemoteIconButtonDefaults.SmallIconSize), imageVector = TestImageVectors.VolumeUp, @@ -129,6 +135,7 @@ class RemoteIconButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteIconButton( + testAction, border = 1.rdp, borderColor = RemoteMaterialTheme.colorScheme.outline, enabled = RemoteBoolean(false), @@ -182,3 +189,5 @@ private fun Center(modifier: RemoteModifier, content: @Composable @RemoteComposa content = content, ) } + +private val testAction = HostAction("testAction".rs, 1.rf) diff --git a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteTextButtonTest.kt b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteTextButtonTest.kt index bd671fd583a01..93416a3430cec 100644 --- a/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteTextButtonTest.kt +++ b/wear/compose/remote/remote-material3/src/androidTest/java/androidx/wear/compose/remote/material3/RemoteTextButtonTest.kt @@ -18,6 +18,7 @@ package androidx.wear.compose.remote.material3 import android.content.Context import androidx.compose.remote.creation.CreationDisplayInfo +import androidx.compose.remote.creation.compose.action.HostAction import androidx.compose.remote.creation.compose.layout.RemoteAlignment import androidx.compose.remote.creation.compose.layout.RemoteArrangement import androidx.compose.remote.creation.compose.layout.RemoteBox @@ -76,7 +77,7 @@ class RemoteTextButtonTest { creationDisplayInfo = creationDisplayInfo, ) { Center(RemoteModifier.fillMaxSize()) { - RemoteTextButton(enabled = false.rb) { RemoteText("ABC".rs) } + RemoteTextButton(testAction, enabled = false.rb) { RemoteText("ABC".rs) } } } } @@ -98,7 +99,7 @@ class RemoteTextButtonTest { creationDisplayInfo = creationDisplayInfo, ) { Center(RemoteModifier.fillMaxSize()) { - RemoteTextButton(enabled = false.rb, colors = FILLED_TONAL_COLOR) { + RemoteTextButton(testAction, enabled = false.rb, colors = FILLED_TONAL_COLOR) { RemoteText("ABC".rs) } } @@ -123,6 +124,7 @@ class RemoteTextButtonTest { ) { Center(RemoteModifier.fillMaxSize()) { RemoteTextButton( + testAction, border = 1.rdp, borderColor = RemoteMaterialTheme.colorScheme.outline, enabled = false.rb, @@ -135,6 +137,8 @@ class RemoteTextButtonTest { } private companion object { + private val testAction = HostAction("testAction".rs, 1.rf) + val FILLED_TONAL_COLOR @Composable get() = diff --git a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteButton.kt b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteButton.kt index 7963eb6bea746..52fd853d22e60 100644 --- a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteButton.kt +++ b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteButton.kt @@ -106,7 +106,7 @@ import androidx.wear.compose.material3.TextConfiguration @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Suppress("RestrictedApiAndroidX") public fun RemoteButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, enabled: RemoteBoolean = true.rb, colors: RemoteButtonColors = RemoteButtonDefaults.buttonColors(), @@ -163,7 +163,7 @@ public fun RemoteButton( @RemoteComposable @Suppress("RestrictedApiAndroidX") public fun RemoteButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, enabled: RemoteBoolean = true.rb, containerPainter: RemotePainter, @@ -254,7 +254,7 @@ public fun RemoteButton( @RemoteComposable @Suppress("RestrictedApiAndroidX") public fun RemoteButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, secondaryLabel: @Composable @RemoteComposable (RemoteRowScope.() -> Unit)? = null, icon: (@Composable () -> Unit)? = null, @@ -367,7 +367,7 @@ public fun RemoteButton( @RemoteComposable @Suppress("RestrictedApiAndroidX") public fun RemoteCompactButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, icon: (@Composable () -> Unit)? = null, enabled: RemoteBoolean = true.rb, @@ -379,14 +379,13 @@ public fun RemoteCompactButton( label: @Composable @RemoteComposable (RemoteRowScope.() -> Unit)?, ) { val tapPadding = RemoteButtonDefaults.CompactButtonTapTargetPadding - val hasActions = onClick.isNotEmpty() RemoteBox( modifier = modifier .compactButtonModifier() .padding(tapPadding) - .clickable(*onClick, enabled = enabled.constantValue ?: false && hasActions) + .clickable(onClick, enabled = enabled.constantValue ?: false) ) { if (label != null) { RemoteButtonImpl( @@ -455,7 +454,7 @@ public fun RemoteCompactButton( @RemoteComposable @Suppress("RestrictedApiAndroidX") private fun RemoteButtonImpl( - vararg onClick: Action, + onClick: Action? = null, modifier: RemoteModifier = RemoteModifier, colors: RemoteButtonColors, containerPainter: RemotePainter?, @@ -468,9 +467,11 @@ private fun RemoteButtonImpl( labelFont: TextStyle, content: @Composable @RemoteComposable RemoteRowScope.() -> Unit, ) { - val hasActions = onClick.isNotEmpty() val containerModifier = - RemoteModifier.clickable(*onClick, enabled = enabled.constantValue ?: false && hasActions) + RemoteModifier.clickable( + actions = buildList { onClick?.let { add(it) } }, + enabled = enabled.constantValue ?: false && onClick != null, + ) .padding(contentPadding) RemoteRow( @@ -503,7 +504,7 @@ private fun RemoteButtonImpl( @RemoteComposable @Suppress("RestrictedApiAndroidX") private fun RemoteButtonImpl( - vararg onClick: Action, + onClick: Action? = null, modifier: RemoteModifier = RemoteModifier, secondaryLabelContent: (@Composable @RemoteComposable RemoteRowScope.() -> Unit)?, icon: (@Composable @RemoteComposable () -> Unit)?, diff --git a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteIconButton.kt b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteIconButton.kt index 868be09e6fd6d..e7808b341739a 100644 --- a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteIconButton.kt +++ b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteIconButton.kt @@ -72,7 +72,7 @@ import androidx.compose.ui.graphics.Color @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Suppress("RestrictedApiAndroidX") public fun RemoteIconButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, colors: RemoteIconButtonColors = RemoteIconButtonDefaults.iconButtonColors(), enabled: RemoteBoolean = true.rb, diff --git a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteRoundButton.kt b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteRoundButton.kt index 69b0e551e292b..118a0c4fbf0e6 100644 --- a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteRoundButton.kt +++ b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteRoundButton.kt @@ -35,7 +35,7 @@ import androidx.compose.runtime.Composable @RemoteComposable @Suppress("RestrictedApiAndroidX") internal fun RemoteRoundButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, backgroundColor: RemoteColor, enabled: RemoteBoolean, @@ -61,7 +61,7 @@ internal fun RemoteRoundButton( ) drawContent() } - .clickable(*onClick, enabled = enabled.constantValue ?: false), + .clickable(onClick, enabled = enabled.constantValue ?: false), content = content, ) } diff --git a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteTextButton.kt b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteTextButton.kt index 7531e870f491a..42d1ea715bb93 100644 --- a/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteTextButton.kt +++ b/wear/compose/remote/remote-material3/src/main/java/androidx/wear/compose/remote/material3/RemoteTextButton.kt @@ -72,7 +72,7 @@ import androidx.wear.compose.material3.TextButtonDefaults @RemoteComposable @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun RemoteTextButton( - vararg onClick: Action, + onClick: Action, modifier: RemoteModifier = RemoteModifier, colors: RemoteTextButtonColors = RemoteTextButtonDefaults.textButtonColors(), enabled: RemoteBoolean = true.rb, diff --git a/wear/watchface/watchface-complications-data-source-ktx/api/1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source-ktx/api/1.3.0-rc01.txt new file mode 100644 index 0000000000000..621eb487902f8 --- /dev/null +++ b/wear/watchface/watchface-complications-data-source-ktx/api/1.3.0-rc01.txt @@ -0,0 +1,17 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.datasource { + + public abstract class SuspendingComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService { + ctor public SuspendingComplicationDataSourceService(); + method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation); + } + + public abstract class SuspendingTimelineComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService { + ctor public SuspendingTimelineComplicationDataSourceService(); + method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation); + } + +} + diff --git a/wear/watchface/watchface-complications-data-source-ktx/api/res-1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source-ktx/api/res-1.3.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/watchface/watchface-complications-data-source-ktx/api/restricted_1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source-ktx/api/restricted_1.3.0-rc01.txt new file mode 100644 index 0000000000000..621eb487902f8 --- /dev/null +++ b/wear/watchface/watchface-complications-data-source-ktx/api/restricted_1.3.0-rc01.txt @@ -0,0 +1,17 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.datasource { + + public abstract class SuspendingComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService { + ctor public SuspendingComplicationDataSourceService(); + method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation); + } + + public abstract class SuspendingTimelineComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService { + ctor public SuspendingTimelineComplicationDataSourceService(); + method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation); + } + +} + diff --git a/wear/watchface/watchface-complications-data-source/api/1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source/api/1.3.0-rc01.txt new file mode 100644 index 0000000000000..ef1e540a0e0a0 --- /dev/null +++ b/wear/watchface/watchface-complications-data-source/api/1.3.0-rc01.txt @@ -0,0 +1,114 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.datasource { + + public abstract class ComplicationDataSourceService extends android.app.Service { + ctor public ComplicationDataSourceService(); + method public abstract androidx.wear.watchface.complications.data.ComplicationData? getPreviewData(androidx.wear.watchface.complications.data.ComplicationType type); + method public final android.os.IBinder? onBind(android.content.Intent intent); + method @MainThread public void onComplicationActivated(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType type); + method @MainThread public void onComplicationDeactivated(int complicationInstanceId); + method @MainThread public abstract void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @MainThread public void onStartImmediateComplicationRequests(int complicationInstanceId); + method @MainThread public void onStopImmediateComplicationRequests(int complicationInstanceId); + field public static final String ACTION_COMPLICATION_UPDATE_REQUEST = "android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"; + field public static final String CATEGORY_DATA_SOURCE_CONFIG = "android.support.wearable.complications.category.PROVIDER_CONFIG"; + field public static final androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion Companion; + field public static final String EXTRA_CONFIG_COMPLICATION_ID = "android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID"; + field public static final String EXTRA_CONFIG_COMPLICATION_TYPE = "android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_TYPE"; + field public static final String EXTRA_CONFIG_DATA_SOURCE_COMPONENT = "android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT"; + field @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final String METADATA_KEY_CONFIG_RESTORE_SUPPORTED = "androidx.watchface.complications.datasource.CONFIG_RESTORE_SUPPORTED"; + field public static final String METADATA_KEY_DATA_SOURCE_CONFIG_ACTION = "android.support.wearable.complications.PROVIDER_CONFIG_ACTION"; + field public static final String METADATA_KEY_DATA_SOURCE_DEFAULT_CONFIG_SUPPORTED = "androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED"; + field public static final String METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS = "androidx.wear.watchface.complications.data.source.IMMEDIATE_UPDATE_PERIOD_MILLISECONDS"; + field public static final String METADATA_KEY_SAFE_WATCH_FACES = "android.support.wearable.complications.SAFE_WATCH_FACES"; + field public static final String METADATA_KEY_SAFE_WATCH_FACE_SUPPORTED_TYPES = "androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"; + field public static final String METADATA_KEY_SUPPORTED_TYPES = "android.support.wearable.complications.SUPPORTED_TYPES"; + field public static final String METADATA_KEY_UPDATE_PERIOD_SECONDS = "android.support.wearable.complications.UPDATE_PERIOD_SECONDS"; + } + + public static final class ComplicationDataSourceService.Companion { + property public static String ACTION_COMPLICATION_UPDATE_REQUEST; + property public static String CATEGORY_DATA_SOURCE_CONFIG; + property public static String EXTRA_CONFIG_COMPLICATION_ID; + property public static String EXTRA_CONFIG_COMPLICATION_TYPE; + property public static String EXTRA_CONFIG_DATA_SOURCE_COMPONENT; + property public static String METADATA_KEY_CONFIG_RESTORE_SUPPORTED; + property public static String METADATA_KEY_DATA_SOURCE_CONFIG_ACTION; + property public static String METADATA_KEY_DATA_SOURCE_DEFAULT_CONFIG_SUPPORTED; + property public static String METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS; + property public static String METADATA_KEY_SAFE_WATCH_FACES; + property public static String METADATA_KEY_SAFE_WATCH_FACE_SUPPORTED_TYPES; + property public static String METADATA_KEY_SUPPORTED_TYPES; + property public static String METADATA_KEY_UPDATE_PERIOD_SECONDS; + } + + @kotlin.jvm.JvmDefaultWithCompatibility public static interface ComplicationDataSourceService.ComplicationRequestListener { + method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void onComplicationData(androidx.wear.watchface.complications.data.ComplicationData? complicationData) throws android.os.RemoteException; + method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default void onComplicationDataTimeline(androidx.wear.watchface.complications.datasource.ComplicationDataTimeline? complicationDataTimeline) throws android.os.RemoteException; + } + + public interface ComplicationDataSourceUpdateRequester { + method public static androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester create(android.content.Context context, android.content.ComponentName complicationDataSourceComponent); + method public void requestUpdate(int... complicationInstanceIds); + method public void requestUpdateAll(); + field public static final androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester.Companion Companion; + } + + public static final class ComplicationDataSourceUpdateRequester.Companion { + method public androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester create(android.content.Context context, android.content.ComponentName complicationDataSourceComponent); + } + + public final class ComplicationDataTimeline { + ctor public ComplicationDataTimeline(androidx.wear.watchface.complications.data.ComplicationData defaultComplicationData, java.util.Collection timelineEntries); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getDefaultComplicationData(); + method @InaccessibleFromKotlin public java.util.Collection getTimelineEntries(); + property public androidx.wear.watchface.complications.data.ComplicationData defaultComplicationData; + property public java.util.Collection timelineEntries; + } + + public final class ComplicationRequest { + ctor @Deprecated public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType); + ctor public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType, boolean immediateResponseRequired); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType, boolean immediateResponseRequired, int isForSafeWatchFace); + method @InaccessibleFromKotlin public int getComplicationInstanceId(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getComplicationType(); + method @InaccessibleFromKotlin public int isForSafeWatchFace(); + method @InaccessibleFromKotlin public boolean isImmediateResponseRequired(); + property public int complicationInstanceId; + property public androidx.wear.watchface.complications.data.ComplicationType complicationType; + property public boolean immediateResponseRequired; + property public int isForSafeWatchFace; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class TargetWatchFaceSafety { + property public static int SAFE; + property public static int UNKNOWN; + property public static int UNSAFE; + field public static final androidx.wear.watchface.complications.datasource.TargetWatchFaceSafety INSTANCE; + field public static final int SAFE = 1; // 0x1 + field public static final int UNKNOWN = 0; // 0x0 + field public static final int UNSAFE = 2; // 0x2 + } + + public final class TimeInterval { + ctor public TimeInterval(java.time.Instant start, java.time.Instant end); + method @InaccessibleFromKotlin public java.time.Instant getEnd(); + method @InaccessibleFromKotlin public java.time.Instant getStart(); + method @InaccessibleFromKotlin public void setEnd(java.time.Instant); + method @InaccessibleFromKotlin public void setStart(java.time.Instant); + property public java.time.Instant end; + property public java.time.Instant start; + } + + public final class TimelineEntry { + ctor public TimelineEntry(androidx.wear.watchface.complications.datasource.TimeInterval validity, androidx.wear.watchface.complications.data.ComplicationData complicationData); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getComplicationData(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.datasource.TimeInterval getValidity(); + method @InaccessibleFromKotlin public void setComplicationData(androidx.wear.watchface.complications.data.ComplicationData); + method @InaccessibleFromKotlin public void setValidity(androidx.wear.watchface.complications.datasource.TimeInterval); + property public androidx.wear.watchface.complications.data.ComplicationData complicationData; + property public androidx.wear.watchface.complications.datasource.TimeInterval validity; + } + +} + diff --git a/wear/watchface/watchface-complications-data-source/api/res-1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source/api/res-1.3.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/watchface/watchface-complications-data-source/api/restricted_1.3.0-rc01.txt b/wear/watchface/watchface-complications-data-source/api/restricted_1.3.0-rc01.txt new file mode 100644 index 0000000000000..ef1e540a0e0a0 --- /dev/null +++ b/wear/watchface/watchface-complications-data-source/api/restricted_1.3.0-rc01.txt @@ -0,0 +1,114 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.datasource { + + public abstract class ComplicationDataSourceService extends android.app.Service { + ctor public ComplicationDataSourceService(); + method public abstract androidx.wear.watchface.complications.data.ComplicationData? getPreviewData(androidx.wear.watchface.complications.data.ComplicationType type); + method public final android.os.IBinder? onBind(android.content.Intent intent); + method @MainThread public void onComplicationActivated(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType type); + method @MainThread public void onComplicationDeactivated(int complicationInstanceId); + method @MainThread public abstract void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener); + method @MainThread public void onStartImmediateComplicationRequests(int complicationInstanceId); + method @MainThread public void onStopImmediateComplicationRequests(int complicationInstanceId); + field public static final String ACTION_COMPLICATION_UPDATE_REQUEST = "android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"; + field public static final String CATEGORY_DATA_SOURCE_CONFIG = "android.support.wearable.complications.category.PROVIDER_CONFIG"; + field public static final androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion Companion; + field public static final String EXTRA_CONFIG_COMPLICATION_ID = "android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID"; + field public static final String EXTRA_CONFIG_COMPLICATION_TYPE = "android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_TYPE"; + field public static final String EXTRA_CONFIG_DATA_SOURCE_COMPONENT = "android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT"; + field @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final String METADATA_KEY_CONFIG_RESTORE_SUPPORTED = "androidx.watchface.complications.datasource.CONFIG_RESTORE_SUPPORTED"; + field public static final String METADATA_KEY_DATA_SOURCE_CONFIG_ACTION = "android.support.wearable.complications.PROVIDER_CONFIG_ACTION"; + field public static final String METADATA_KEY_DATA_SOURCE_DEFAULT_CONFIG_SUPPORTED = "androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED"; + field public static final String METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS = "androidx.wear.watchface.complications.data.source.IMMEDIATE_UPDATE_PERIOD_MILLISECONDS"; + field public static final String METADATA_KEY_SAFE_WATCH_FACES = "android.support.wearable.complications.SAFE_WATCH_FACES"; + field public static final String METADATA_KEY_SAFE_WATCH_FACE_SUPPORTED_TYPES = "androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"; + field public static final String METADATA_KEY_SUPPORTED_TYPES = "android.support.wearable.complications.SUPPORTED_TYPES"; + field public static final String METADATA_KEY_UPDATE_PERIOD_SECONDS = "android.support.wearable.complications.UPDATE_PERIOD_SECONDS"; + } + + public static final class ComplicationDataSourceService.Companion { + property public static String ACTION_COMPLICATION_UPDATE_REQUEST; + property public static String CATEGORY_DATA_SOURCE_CONFIG; + property public static String EXTRA_CONFIG_COMPLICATION_ID; + property public static String EXTRA_CONFIG_COMPLICATION_TYPE; + property public static String EXTRA_CONFIG_DATA_SOURCE_COMPONENT; + property public static String METADATA_KEY_CONFIG_RESTORE_SUPPORTED; + property public static String METADATA_KEY_DATA_SOURCE_CONFIG_ACTION; + property public static String METADATA_KEY_DATA_SOURCE_DEFAULT_CONFIG_SUPPORTED; + property public static String METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS; + property public static String METADATA_KEY_SAFE_WATCH_FACES; + property public static String METADATA_KEY_SAFE_WATCH_FACE_SUPPORTED_TYPES; + property public static String METADATA_KEY_SUPPORTED_TYPES; + property public static String METADATA_KEY_UPDATE_PERIOD_SECONDS; + } + + @kotlin.jvm.JvmDefaultWithCompatibility public static interface ComplicationDataSourceService.ComplicationRequestListener { + method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void onComplicationData(androidx.wear.watchface.complications.data.ComplicationData? complicationData) throws android.os.RemoteException; + method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default void onComplicationDataTimeline(androidx.wear.watchface.complications.datasource.ComplicationDataTimeline? complicationDataTimeline) throws android.os.RemoteException; + } + + public interface ComplicationDataSourceUpdateRequester { + method public static androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester create(android.content.Context context, android.content.ComponentName complicationDataSourceComponent); + method public void requestUpdate(int... complicationInstanceIds); + method public void requestUpdateAll(); + field public static final androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester.Companion Companion; + } + + public static final class ComplicationDataSourceUpdateRequester.Companion { + method public androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester create(android.content.Context context, android.content.ComponentName complicationDataSourceComponent); + } + + public final class ComplicationDataTimeline { + ctor public ComplicationDataTimeline(androidx.wear.watchface.complications.data.ComplicationData defaultComplicationData, java.util.Collection timelineEntries); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getDefaultComplicationData(); + method @InaccessibleFromKotlin public java.util.Collection getTimelineEntries(); + property public androidx.wear.watchface.complications.data.ComplicationData defaultComplicationData; + property public java.util.Collection timelineEntries; + } + + public final class ComplicationRequest { + ctor @Deprecated public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType); + ctor public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType, boolean immediateResponseRequired); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public ComplicationRequest(int complicationInstanceId, androidx.wear.watchface.complications.data.ComplicationType complicationType, boolean immediateResponseRequired, int isForSafeWatchFace); + method @InaccessibleFromKotlin public int getComplicationInstanceId(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getComplicationType(); + method @InaccessibleFromKotlin public int isForSafeWatchFace(); + method @InaccessibleFromKotlin public boolean isImmediateResponseRequired(); + property public int complicationInstanceId; + property public androidx.wear.watchface.complications.data.ComplicationType complicationType; + property public boolean immediateResponseRequired; + property public int isForSafeWatchFace; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class TargetWatchFaceSafety { + property public static int SAFE; + property public static int UNKNOWN; + property public static int UNSAFE; + field public static final androidx.wear.watchface.complications.datasource.TargetWatchFaceSafety INSTANCE; + field public static final int SAFE = 1; // 0x1 + field public static final int UNKNOWN = 0; // 0x0 + field public static final int UNSAFE = 2; // 0x2 + } + + public final class TimeInterval { + ctor public TimeInterval(java.time.Instant start, java.time.Instant end); + method @InaccessibleFromKotlin public java.time.Instant getEnd(); + method @InaccessibleFromKotlin public java.time.Instant getStart(); + method @InaccessibleFromKotlin public void setEnd(java.time.Instant); + method @InaccessibleFromKotlin public void setStart(java.time.Instant); + property public java.time.Instant end; + property public java.time.Instant start; + } + + public final class TimelineEntry { + ctor public TimelineEntry(androidx.wear.watchface.complications.datasource.TimeInterval validity, androidx.wear.watchface.complications.data.ComplicationData complicationData); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getComplicationData(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.datasource.TimeInterval getValidity(); + method @InaccessibleFromKotlin public void setComplicationData(androidx.wear.watchface.complications.data.ComplicationData); + method @InaccessibleFromKotlin public void setValidity(androidx.wear.watchface.complications.datasource.TimeInterval); + property public androidx.wear.watchface.complications.data.ComplicationData complicationData; + property public androidx.wear.watchface.complications.datasource.TimeInterval validity; + } + +} + diff --git a/wear/watchface/watchface-complications-data/api/1.3.0-rc01.txt b/wear/watchface/watchface-complications-data/api/1.3.0-rc01.txt new file mode 100644 index 0000000000000..b184391da2f62 --- /dev/null +++ b/wear/watchface/watchface-complications-data/api/1.3.0-rc01.txt @@ -0,0 +1,655 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.data { + + public final class ColorRamp { + ctor public ColorRamp(@ColorInt int[] colors, boolean interpolated); + method @InaccessibleFromKotlin public int[] getColors(); + method @InaccessibleFromKotlin public boolean isInterpolated(); + property public int[] colors; + property public boolean interpolated; + } + + public abstract sealed exhaustive class ComplicationData { + method @InaccessibleFromKotlin public final android.content.ComponentName? getDataSource(); + method @InaccessibleFromKotlin public final int getDisplayPolicy(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.ComplicationData? getDynamicValueInvalidationFallback(); + method @InaccessibleFromKotlin public final android.os.PersistableBundle getExtras(); + method public java.time.Instant getNextChangeInstant(java.time.Instant afterInstant); + method @InaccessibleFromKotlin public final int getPersistencePolicy(); + method @InaccessibleFromKotlin public final android.app.PendingIntent? getTapAction(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.ComplicationType getType(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.TimeRange getValidTimeRange(); + method public boolean hasPlaceholderFields(); + method @InaccessibleFromKotlin public final boolean isTapActionLostDueToSerialization(); + method @InaccessibleFromKotlin public final void setTapActionLostDueToSerialization(boolean); + property public final android.content.ComponentName? dataSource; + property public final int displayPolicy; + property public final androidx.wear.watchface.complications.data.ComplicationData? dynamicValueInvalidationFallback; + property public final android.os.PersistableBundle extras; + property public final int persistencePolicy; + property public final android.app.PendingIntent? tapAction; + property public final boolean tapActionLostDueToSerialization; + property public final androidx.wear.watchface.complications.data.ComplicationType type; + property public final androidx.wear.watchface.complications.data.TimeRange validTimeRange; + } + + public final class ComplicationDisplayPolicies { + property public static int ALWAYS_DISPLAY; + property public static int DO_NOT_SHOW_WHEN_DEVICE_LOCKED; + field public static final int ALWAYS_DISPLAY = 0; // 0x0 + field public static final int DO_NOT_SHOW_WHEN_DEVICE_LOCKED = 1; // 0x1 + field public static final androidx.wear.watchface.complications.data.ComplicationDisplayPolicies INSTANCE; + } + + @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ComplicationExperimental { + } + + public final class ComplicationPersistencePolicies { + property public static int CACHING_ALLOWED; + property public static int DO_NOT_PERSIST; + field public static final int CACHING_ALLOWED = 0; // 0x0 + field public static final int DO_NOT_PERSIST = 1; // 0x1 + field public static final androidx.wear.watchface.complications.data.ComplicationPersistencePolicies INSTANCE; + } + + @kotlin.jvm.JvmDefaultWithCompatibility public interface ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + field public static final androidx.wear.watchface.complications.data.ComplicationText.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationText EMPTY; + field public static final androidx.wear.watchface.complications.data.ComplicationText PLACEHOLDER; + } + + public static final class ComplicationText.Companion { + property public androidx.wear.watchface.complications.data.ComplicationText EMPTY; + property public androidx.wear.watchface.complications.data.ComplicationText PLACEHOLDER; + } + + public enum ComplicationType { + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY; + enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType GOAL_PROGRESS; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType MONOCHROMATIC_IMAGE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NOT_CONFIGURED; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_DATA; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_PERMISSION; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType PHOTO_IMAGE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType RANGED_VALUE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SHORT_TEXT; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SMALL_IMAGE; + enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType WEIGHTED_ELEMENTS; + field public static final androidx.wear.watchface.complications.data.ComplicationType.Companion Companion; + } + + public static final class ComplicationType.Companion { + } + + public final class CountDownTimeReference { + ctor public CountDownTimeReference(java.time.Instant instant); + method @InaccessibleFromKotlin public java.time.Instant getInstant(); + property public java.time.Instant instant; + } + + public final class CountUpTimeReference { + ctor public CountUpTimeReference(java.time.Instant instant); + method @InaccessibleFromKotlin public java.time.Instant getInstant(); + property public java.time.Instant instant; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue); + ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue); + method @InaccessibleFromKotlin public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue(); + method @InaccessibleFromKotlin public CharSequence getFallbackValue(); + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + property public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue; + property public CharSequence fallbackValue; + } + + public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public EmptyComplicationData(); + field public static final androidx.wear.watchface.complications.data.EmptyComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class EmptyComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public float getTargetValue(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + method @InaccessibleFromKotlin public float getValue(); + property public androidx.wear.watchface.complications.data.ColorRamp? colorRamp; + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicValue; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public float targetValue; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + property public float value; + field public static final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Companion Companion; + field public static final float PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final class GoalProgressComplicationData.Builder { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public GoalProgressComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public GoalProgressComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor public GoalProgressComplicationData.Builder(float value, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData build(); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp); + method public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.GoalProgressComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class GoalProgressComplicationData.Companion { + property public float PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class LongTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.LongTextComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class LongTextComplicationData.Builder { + ctor public LongTextComplicationData.Builder(androidx.wear.watchface.complications.data.ComplicationText text, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.LongTextComplicationData build(); + method public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.LongTextComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? icon); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class LongTextComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class MonochromaticImage { + method @InaccessibleFromKotlin public android.graphics.drawable.Icon? getAmbientImage(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getImage(); + property public android.graphics.drawable.Icon? ambientImage; + property public android.graphics.drawable.Icon image; + field public static final androidx.wear.watchface.complications.data.MonochromaticImage.Companion Companion; + field public static final androidx.wear.watchface.complications.data.MonochromaticImage PLACEHOLDER; + } + + public static final class MonochromaticImage.Builder { + ctor public MonochromaticImage.Builder(android.graphics.drawable.Icon image); + method public androidx.wear.watchface.complications.data.MonochromaticImage build(); + method public androidx.wear.watchface.complications.data.MonochromaticImage.Builder setAmbientImage(android.graphics.drawable.Icon? ambientImage); + } + + public static final class MonochromaticImage.Companion { + property public androidx.wear.watchface.complications.data.MonochromaticImage PLACEHOLDER; + } + + public final class MonochromaticImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage getMonochromaticImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage monochromaticImage; + field public static final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class MonochromaticImageComplicationData.Builder { + ctor public MonochromaticImageComplicationData.Builder(androidx.wear.watchface.complications.data.MonochromaticImage monochromaticImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.MonochromaticImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class MonochromaticImageComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NoDataComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public NoDataComplicationData(); + ctor public NoDataComplicationData(androidx.wear.watchface.complications.data.ComplicationData placeholder); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData? getInvalidatedData(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData? getPlaceholder(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.ComplicationData? invalidatedData; + property public androidx.wear.watchface.complications.data.ComplicationData? placeholder; + field public static final androidx.wear.watchface.complications.data.NoDataComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NoDataComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NoPermissionComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NoPermissionComplicationData.Builder { + ctor public NoPermissionComplicationData.Builder(); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData build(); + method public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.NoPermissionComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + } + + public static final class NoPermissionComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NotConfiguredComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public NotConfiguredComplicationData(); + field public static final androidx.wear.watchface.complications.data.NotConfiguredComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NotConfiguredComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class PhotoImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getPhotoImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public android.graphics.drawable.Icon photoImage; + field public static final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Companion Companion; + field public static final android.graphics.drawable.Icon PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class PhotoImageComplicationData.Builder { + ctor public PhotoImageComplicationData.Builder(android.graphics.drawable.Icon photoImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.PhotoImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class PhotoImageComplicationData.Companion { + property public android.graphics.drawable.Icon PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class PlainComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class PlainComplicationText.Builder { + ctor public PlainComplicationText.Builder(CharSequence text); + method public androidx.wear.watchface.complications.data.PlainComplicationText build(); + } + + public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue(); + method @InaccessibleFromKotlin public float getMax(); + method @InaccessibleFromKotlin public float getMin(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + method @InaccessibleFromKotlin public float getValue(); + method @InaccessibleFromKotlin public int getValueType(); + property public androidx.wear.watchface.complications.data.ColorRamp? colorRamp; + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicValue; + property public float max; + property public float min; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + property public float value; + property public int valueType; + field public static final androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion Companion; + field public static final float PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + field public static final int TYPE_PERCENTAGE = 2; // 0x2 + field public static final int TYPE_RATING = 1; // 0x1 + field public static final int TYPE_UNDEFINED = 0; // 0x0 + } + + public static final class RangedValueComplicationData.Builder { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData build(); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp); + method public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.RangedValueComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValueType(int valueType); + } + + public static final class RangedValueComplicationData.Companion { + property public float PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + property public static int TYPE_PERCENTAGE; + property public static int TYPE_RATING; + property public static int TYPE_UNDEFINED; + } + + public final class ShortTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.ShortTextComplicationData.Companion Companion; + field public static final int MAX_TEXT_LENGTH; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class ShortTextComplicationData.Builder { + ctor public ShortTextComplicationData.Builder(androidx.wear.watchface.complications.data.ComplicationText text, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData build(); + method public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.ShortTextComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class ShortTextComplicationData.Companion { + property public int MAX_TEXT_LENGTH; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class SmallImage { + method @InaccessibleFromKotlin public android.graphics.drawable.Icon? getAmbientImage(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImageType getType(); + property public android.graphics.drawable.Icon? ambientImage; + property public android.graphics.drawable.Icon image; + property public androidx.wear.watchface.complications.data.SmallImageType type; + field public static final androidx.wear.watchface.complications.data.SmallImage.Companion Companion; + field public static final androidx.wear.watchface.complications.data.SmallImage PLACEHOLDER; + } + + public static final class SmallImage.Builder { + ctor public SmallImage.Builder(android.graphics.drawable.Icon image, androidx.wear.watchface.complications.data.SmallImageType type); + method public androidx.wear.watchface.complications.data.SmallImage build(); + method public androidx.wear.watchface.complications.data.SmallImage.Builder setAmbientImage(android.graphics.drawable.Icon? ambientImage); + } + + public static final class SmallImage.Companion { + property public androidx.wear.watchface.complications.data.SmallImage PLACEHOLDER; + } + + public final class SmallImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage getSmallImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.SmallImage smallImage; + field public static final androidx.wear.watchface.complications.data.SmallImageComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class SmallImageComplicationData.Builder { + ctor public SmallImageComplicationData.Builder(androidx.wear.watchface.complications.data.SmallImage smallImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.SmallImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class SmallImageComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public enum SmallImageType { + enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType ICON; + enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType PHOTO; + } + + public final class TimeDifferenceComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.util.concurrent.TimeUnit? getMinimumTimeUnit(); + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class TimeDifferenceComplicationText.Builder { + ctor public TimeDifferenceComplicationText.Builder(androidx.wear.watchface.complications.data.TimeDifferenceStyle style, androidx.wear.watchface.complications.data.CountDownTimeReference countDownTimeReference); + ctor public TimeDifferenceComplicationText.Builder(androidx.wear.watchface.complications.data.TimeDifferenceStyle style, androidx.wear.watchface.complications.data.CountUpTimeReference countUpTimeReference); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText build(); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setDisplayAsNow(boolean displayAsNow); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setMinimumTimeUnit(java.util.concurrent.TimeUnit? minimumUnit); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setText(CharSequence? text); + } + + public enum TimeDifferenceStyle { + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_DUAL_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_SINGLE_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_WORDS_SINGLE_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle STOPWATCH; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle WORDS_SINGLE_UNIT; + } + + public final class TimeFormatComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class TimeFormatComplicationText.Builder { + ctor public TimeFormatComplicationText.Builder(String format); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText build(); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setStyle(androidx.wear.watchface.complications.data.TimeFormatStyle style); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setText(CharSequence text); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setTimeZone(android.icu.util.TimeZone timeZone); + } + + public enum TimeFormatStyle { + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle DEFAULT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle LOWER_CASE; + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle UPPER_CASE; + } + + public final class TimeRange { + method public static androidx.wear.watchface.complications.data.TimeRange after(java.time.Instant startInstant); + method public static androidx.wear.watchface.complications.data.TimeRange before(java.time.Instant endInstant); + method public static androidx.wear.watchface.complications.data.TimeRange between(java.time.Instant startInstant, java.time.Instant endInstant); + method public operator boolean contains(java.time.Instant dateTimeMillis); + method @InaccessibleFromKotlin public java.time.Instant getEndDateTimeMillis(); + method @InaccessibleFromKotlin public java.time.Instant getStartDateTimeMillis(); + property public java.time.Instant endDateTimeMillis; + property public java.time.Instant startDateTimeMillis; + field public static final androidx.wear.watchface.complications.data.TimeRange ALWAYS; + field public static final androidx.wear.watchface.complications.data.TimeRange.Companion Companion; + } + + public static final class TimeRange.Companion { + method public androidx.wear.watchface.complications.data.TimeRange after(java.time.Instant startInstant); + method public androidx.wear.watchface.complications.data.TimeRange before(java.time.Instant endInstant); + method public androidx.wear.watchface.complications.data.TimeRange between(java.time.Instant startInstant, java.time.Instant endInstant); + property public androidx.wear.watchface.complications.data.TimeRange ALWAYS; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class WeightedElementsComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public int getElementBackgroundColor(); + method @InaccessibleFromKotlin public java.util.List getElements(); + method public static int getMaxElements(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public int elementBackgroundColor; + property public java.util.List elements; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Companion Companion; + field public static final java.util.List PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final class WeightedElementsComplicationData.Builder { + ctor public WeightedElementsComplicationData.Builder(java.util.List elements, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData build(); + method public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.WeightedElementsComplicationData? fallback); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setElementBackgroundColor(@ColorInt int elementBackgroundColor); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class WeightedElementsComplicationData.Companion { + method public int getMaxElements(); + property public java.util.List PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class WeightedElementsComplicationData.Element { + ctor public WeightedElementsComplicationData.Element(@FloatRange(from=0.0, fromInclusive=false) float weight, @ColorInt int color); + method @InaccessibleFromKotlin public int getColor(); + method @InaccessibleFromKotlin public float getWeight(); + property public int color; + property public float weight; + } + +} + +package androidx.wear.watchface.complications.data.formatting { + + public final class ComplicationTextFormatting { + ctor public ComplicationTextFormatting(java.util.Locale locale); + method public String? getBestShortTextDateFormat(String[] skeletons, String? fallback); + method public String? getFormattedDayOfWeekForShortText(long timeInMillis, android.icu.util.TimeZone? timeZone); + method public String? getFormattedTimeForShortText(long timeInMillis, android.icu.util.TimeZone? timeZone, boolean use24Hour); + method @InaccessibleFromKotlin public String getFullTextDayOfWeekFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayOfMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayOfWeekFormat(); + method @InaccessibleFromKotlin public String? getShortTextMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat12Hour(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat12HourWithoutAmPmShortening(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat24Hour(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat24HourWithoutAmPmShortening(); + property public String fullTextDayOfWeekFormat; + property public String? shortTextDayMonthFormat; + property public String? shortTextDayOfMonthFormat; + property public String? shortTextDayOfWeekFormat; + property public String? shortTextMonthFormat; + property public String? shortTextTimeFormat12Hour; + property public String? shortTextTimeFormat12HourWithoutAmPmShortening; + property public String? shortTextTimeFormat24Hour; + property public String? shortTextTimeFormat24HourWithoutAmPmShortening; + } + +} + +package androidx.wear.watchface.complications.data.parser { + + public final class PreviewData { + method public operator androidx.wear.watchface.complications.data.ComplicationData? get(androidx.wear.watchface.complications.data.ComplicationType type); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) @kotlin.jvm.Throws(exceptionClasses={XmlPullParserException::class, IOException::class}) public static androidx.wear.watchface.complications.data.parser.PreviewData inflate(android.content.Context providerContext, android.content.res.XmlResourceParser parser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + + public final class StaticPreviewDataParser { + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static androidx.wear.watchface.complications.data.parser.PreviewData? parsePreviewData(android.content.Context context, android.content.ComponentName providerComponent); + field public static final androidx.wear.watchface.complications.data.parser.StaticPreviewDataParser INSTANCE; + } + +} + diff --git a/wear/watchface/watchface-complications-data/api/res-1.3.0-rc01.txt b/wear/watchface/watchface-complications-data/api/res-1.3.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/watchface/watchface-complications-data/api/restricted_1.3.0-rc01.txt b/wear/watchface/watchface-complications-data/api/restricted_1.3.0-rc01.txt new file mode 100644 index 0000000000000..b184391da2f62 --- /dev/null +++ b/wear/watchface/watchface-complications-data/api/restricted_1.3.0-rc01.txt @@ -0,0 +1,655 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications.data { + + public final class ColorRamp { + ctor public ColorRamp(@ColorInt int[] colors, boolean interpolated); + method @InaccessibleFromKotlin public int[] getColors(); + method @InaccessibleFromKotlin public boolean isInterpolated(); + property public int[] colors; + property public boolean interpolated; + } + + public abstract sealed exhaustive class ComplicationData { + method @InaccessibleFromKotlin public final android.content.ComponentName? getDataSource(); + method @InaccessibleFromKotlin public final int getDisplayPolicy(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.ComplicationData? getDynamicValueInvalidationFallback(); + method @InaccessibleFromKotlin public final android.os.PersistableBundle getExtras(); + method public java.time.Instant getNextChangeInstant(java.time.Instant afterInstant); + method @InaccessibleFromKotlin public final int getPersistencePolicy(); + method @InaccessibleFromKotlin public final android.app.PendingIntent? getTapAction(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.ComplicationType getType(); + method @InaccessibleFromKotlin public final androidx.wear.watchface.complications.data.TimeRange getValidTimeRange(); + method public boolean hasPlaceholderFields(); + method @InaccessibleFromKotlin public final boolean isTapActionLostDueToSerialization(); + method @InaccessibleFromKotlin public final void setTapActionLostDueToSerialization(boolean); + property public final android.content.ComponentName? dataSource; + property public final int displayPolicy; + property public final androidx.wear.watchface.complications.data.ComplicationData? dynamicValueInvalidationFallback; + property public final android.os.PersistableBundle extras; + property public final int persistencePolicy; + property public final android.app.PendingIntent? tapAction; + property public final boolean tapActionLostDueToSerialization; + property public final androidx.wear.watchface.complications.data.ComplicationType type; + property public final androidx.wear.watchface.complications.data.TimeRange validTimeRange; + } + + public final class ComplicationDisplayPolicies { + property public static int ALWAYS_DISPLAY; + property public static int DO_NOT_SHOW_WHEN_DEVICE_LOCKED; + field public static final int ALWAYS_DISPLAY = 0; // 0x0 + field public static final int DO_NOT_SHOW_WHEN_DEVICE_LOCKED = 1; // 0x1 + field public static final androidx.wear.watchface.complications.data.ComplicationDisplayPolicies INSTANCE; + } + + @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ComplicationExperimental { + } + + public final class ComplicationPersistencePolicies { + property public static int CACHING_ALLOWED; + property public static int DO_NOT_PERSIST; + field public static final int CACHING_ALLOWED = 0; // 0x0 + field public static final int DO_NOT_PERSIST = 1; // 0x1 + field public static final androidx.wear.watchface.complications.data.ComplicationPersistencePolicies INSTANCE; + } + + @kotlin.jvm.JvmDefaultWithCompatibility public interface ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + field public static final androidx.wear.watchface.complications.data.ComplicationText.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationText EMPTY; + field public static final androidx.wear.watchface.complications.data.ComplicationText PLACEHOLDER; + } + + public static final class ComplicationText.Companion { + property public androidx.wear.watchface.complications.data.ComplicationText EMPTY; + property public androidx.wear.watchface.complications.data.ComplicationText PLACEHOLDER; + } + + public enum ComplicationType { + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY; + enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType GOAL_PROGRESS; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType MONOCHROMATIC_IMAGE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NOT_CONFIGURED; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_DATA; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_PERMISSION; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType PHOTO_IMAGE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType RANGED_VALUE; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SHORT_TEXT; + enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SMALL_IMAGE; + enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType WEIGHTED_ELEMENTS; + field public static final androidx.wear.watchface.complications.data.ComplicationType.Companion Companion; + } + + public static final class ComplicationType.Companion { + } + + public final class CountDownTimeReference { + ctor public CountDownTimeReference(java.time.Instant instant); + method @InaccessibleFromKotlin public java.time.Instant getInstant(); + property public java.time.Instant instant; + } + + public final class CountUpTimeReference { + ctor public CountUpTimeReference(java.time.Instant instant); + method @InaccessibleFromKotlin public java.time.Instant getInstant(); + property public java.time.Instant instant; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue); + ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue); + method @InaccessibleFromKotlin public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue(); + method @InaccessibleFromKotlin public CharSequence getFallbackValue(); + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + property public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue; + property public CharSequence fallbackValue; + } + + public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public EmptyComplicationData(); + field public static final androidx.wear.watchface.complications.data.EmptyComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class EmptyComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public float getTargetValue(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + method @InaccessibleFromKotlin public float getValue(); + property public androidx.wear.watchface.complications.data.ColorRamp? colorRamp; + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicValue; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public float targetValue; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + property public float value; + field public static final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Companion Companion; + field public static final float PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final class GoalProgressComplicationData.Builder { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public GoalProgressComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public GoalProgressComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor public GoalProgressComplicationData.Builder(float value, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData build(); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp); + method public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.GoalProgressComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class GoalProgressComplicationData.Companion { + property public float PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class LongTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.LongTextComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class LongTextComplicationData.Builder { + ctor public LongTextComplicationData.Builder(androidx.wear.watchface.complications.data.ComplicationText text, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.LongTextComplicationData build(); + method public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.LongTextComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? icon); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.LongTextComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class LongTextComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class MonochromaticImage { + method @InaccessibleFromKotlin public android.graphics.drawable.Icon? getAmbientImage(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getImage(); + property public android.graphics.drawable.Icon? ambientImage; + property public android.graphics.drawable.Icon image; + field public static final androidx.wear.watchface.complications.data.MonochromaticImage.Companion Companion; + field public static final androidx.wear.watchface.complications.data.MonochromaticImage PLACEHOLDER; + } + + public static final class MonochromaticImage.Builder { + ctor public MonochromaticImage.Builder(android.graphics.drawable.Icon image); + method public androidx.wear.watchface.complications.data.MonochromaticImage build(); + method public androidx.wear.watchface.complications.data.MonochromaticImage.Builder setAmbientImage(android.graphics.drawable.Icon? ambientImage); + } + + public static final class MonochromaticImage.Companion { + property public androidx.wear.watchface.complications.data.MonochromaticImage PLACEHOLDER; + } + + public final class MonochromaticImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage getMonochromaticImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage monochromaticImage; + field public static final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class MonochromaticImageComplicationData.Builder { + ctor public MonochromaticImageComplicationData.Builder(androidx.wear.watchface.complications.data.MonochromaticImage monochromaticImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.MonochromaticImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.MonochromaticImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class MonochromaticImageComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NoDataComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public NoDataComplicationData(); + ctor public NoDataComplicationData(androidx.wear.watchface.complications.data.ComplicationData placeholder); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData? getInvalidatedData(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData? getPlaceholder(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.ComplicationData? invalidatedData; + property public androidx.wear.watchface.complications.data.ComplicationData? placeholder; + field public static final androidx.wear.watchface.complications.data.NoDataComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NoDataComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NoPermissionComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NoPermissionComplicationData.Builder { + ctor public NoPermissionComplicationData.Builder(); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData build(); + method public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.NoPermissionComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.NoPermissionComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + } + + public static final class NoPermissionComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class NotConfiguredComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + ctor public NotConfiguredComplicationData(); + field public static final androidx.wear.watchface.complications.data.NotConfiguredComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class NotConfiguredComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class PhotoImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getPhotoImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public android.graphics.drawable.Icon photoImage; + field public static final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Companion Companion; + field public static final android.graphics.drawable.Icon PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class PhotoImageComplicationData.Builder { + ctor public PhotoImageComplicationData.Builder(android.graphics.drawable.Icon photoImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.PhotoImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.PhotoImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class PhotoImageComplicationData.Companion { + property public android.graphics.drawable.Icon PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class PlainComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class PlainComplicationText.Builder { + ctor public PlainComplicationText.Builder(CharSequence text); + method public androidx.wear.watchface.complications.data.PlainComplicationText build(); + } + + public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue(); + method @InaccessibleFromKotlin public float getMax(); + method @InaccessibleFromKotlin public float getMin(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + method @InaccessibleFromKotlin public float getValue(); + method @InaccessibleFromKotlin public int getValueType(); + property public androidx.wear.watchface.complications.data.ColorRamp? colorRamp; + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicValue; + property public float max; + property public float min; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + property public float value; + property public int valueType; + field public static final androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion Companion; + field public static final float PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + field public static final int TYPE_PERCENTAGE = 2; // 0x2 + field public static final int TYPE_RATING = 1; // 0x1 + field public static final int TYPE_UNDEFINED = 0; // 0x0 + } + + public static final class RangedValueComplicationData.Builder { + ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData build(); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp); + method public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.RangedValueComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValueType(int valueType); + } + + public static final class RangedValueComplicationData.Companion { + property public float PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + property public static int TYPE_PERCENTAGE; + property public static int TYPE_RATING; + property public static int TYPE_UNDEFINED; + } + + public final class ShortTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.ShortTextComplicationData.Companion Companion; + field public static final int MAX_TEXT_LENGTH; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class ShortTextComplicationData.Builder { + ctor public ShortTextComplicationData.Builder(androidx.wear.watchface.complications.data.ComplicationText text, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData build(); + method public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.ShortTextComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.ShortTextComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class ShortTextComplicationData.Companion { + property public int MAX_TEXT_LENGTH; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public final class SmallImage { + method @InaccessibleFromKotlin public android.graphics.drawable.Icon? getAmbientImage(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImageType getType(); + property public android.graphics.drawable.Icon? ambientImage; + property public android.graphics.drawable.Icon image; + property public androidx.wear.watchface.complications.data.SmallImageType type; + field public static final androidx.wear.watchface.complications.data.SmallImage.Companion Companion; + field public static final androidx.wear.watchface.complications.data.SmallImage PLACEHOLDER; + } + + public static final class SmallImage.Builder { + ctor public SmallImage.Builder(android.graphics.drawable.Icon image, androidx.wear.watchface.complications.data.SmallImageType type); + method public androidx.wear.watchface.complications.data.SmallImage build(); + method public androidx.wear.watchface.complications.data.SmallImage.Builder setAmbientImage(android.graphics.drawable.Icon? ambientImage); + } + + public static final class SmallImage.Companion { + property public androidx.wear.watchface.complications.data.SmallImage PLACEHOLDER; + } + + public final class SmallImageComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage getSmallImage(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public androidx.wear.watchface.complications.data.SmallImage smallImage; + field public static final androidx.wear.watchface.complications.data.SmallImageComplicationData.Companion Companion; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class SmallImageComplicationData.Builder { + ctor public SmallImageComplicationData.Builder(androidx.wear.watchface.complications.data.SmallImage smallImage, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData build(); + method public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.SmallImageComplicationData? fallback); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.SmallImageComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class SmallImageComplicationData.Companion { + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public enum SmallImageType { + enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType ICON; + enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType PHOTO; + } + + public final class TimeDifferenceComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.util.concurrent.TimeUnit? getMinimumTimeUnit(); + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class TimeDifferenceComplicationText.Builder { + ctor public TimeDifferenceComplicationText.Builder(androidx.wear.watchface.complications.data.TimeDifferenceStyle style, androidx.wear.watchface.complications.data.CountDownTimeReference countDownTimeReference); + ctor public TimeDifferenceComplicationText.Builder(androidx.wear.watchface.complications.data.TimeDifferenceStyle style, androidx.wear.watchface.complications.data.CountUpTimeReference countUpTimeReference); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText build(); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setDisplayAsNow(boolean displayAsNow); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setMinimumTimeUnit(java.util.concurrent.TimeUnit? minimumUnit); + method public androidx.wear.watchface.complications.data.TimeDifferenceComplicationText.Builder setText(CharSequence? text); + } + + public enum TimeDifferenceStyle { + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_DUAL_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_SINGLE_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_WORDS_SINGLE_UNIT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle STOPWATCH; + enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle WORDS_SINGLE_UNIT; + } + + public final class TimeFormatComplicationText implements androidx.wear.watchface.complications.data.ComplicationText { + method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant); + method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant); + method public boolean isAlwaysEmpty(); + method public boolean returnsSameText(java.time.Instant firstInstant, java.time.Instant secondInstant); + } + + public static final class TimeFormatComplicationText.Builder { + ctor public TimeFormatComplicationText.Builder(String format); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText build(); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setStyle(androidx.wear.watchface.complications.data.TimeFormatStyle style); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setText(CharSequence text); + method public androidx.wear.watchface.complications.data.TimeFormatComplicationText.Builder setTimeZone(android.icu.util.TimeZone timeZone); + } + + public enum TimeFormatStyle { + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle DEFAULT; + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle LOWER_CASE; + enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle UPPER_CASE; + } + + public final class TimeRange { + method public static androidx.wear.watchface.complications.data.TimeRange after(java.time.Instant startInstant); + method public static androidx.wear.watchface.complications.data.TimeRange before(java.time.Instant endInstant); + method public static androidx.wear.watchface.complications.data.TimeRange between(java.time.Instant startInstant, java.time.Instant endInstant); + method public operator boolean contains(java.time.Instant dateTimeMillis); + method @InaccessibleFromKotlin public java.time.Instant getEndDateTimeMillis(); + method @InaccessibleFromKotlin public java.time.Instant getStartDateTimeMillis(); + property public java.time.Instant endDateTimeMillis; + property public java.time.Instant startDateTimeMillis; + field public static final androidx.wear.watchface.complications.data.TimeRange ALWAYS; + field public static final androidx.wear.watchface.complications.data.TimeRange.Companion Companion; + } + + public static final class TimeRange.Companion { + method public androidx.wear.watchface.complications.data.TimeRange after(java.time.Instant startInstant); + method public androidx.wear.watchface.complications.data.TimeRange before(java.time.Instant endInstant); + method public androidx.wear.watchface.complications.data.TimeRange between(java.time.Instant startInstant, java.time.Instant endInstant); + property public androidx.wear.watchface.complications.data.TimeRange ALWAYS; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class WeightedElementsComplicationData extends androidx.wear.watchface.complications.data.ComplicationData { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription(); + method @InaccessibleFromKotlin public int getElementBackgroundColor(); + method @InaccessibleFromKotlin public java.util.List getElements(); + method public static int getMaxElements(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.SmallImage? getSmallImage(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getText(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationText? getTitle(); + property public androidx.wear.watchface.complications.data.ComplicationText? contentDescription; + property public int elementBackgroundColor; + property public java.util.List elements; + property public androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage; + property public androidx.wear.watchface.complications.data.SmallImage? smallImage; + property public androidx.wear.watchface.complications.data.ComplicationText? text; + property public androidx.wear.watchface.complications.data.ComplicationText? title; + field public static final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Companion Companion; + field public static final java.util.List PLACEHOLDER; + field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final class WeightedElementsComplicationData.Builder { + ctor public WeightedElementsComplicationData.Builder(java.util.List elements, androidx.wear.watchface.complications.data.ComplicationText contentDescription); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData build(); + method public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDataSource(android.content.ComponentName? dataSource); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDisplayPolicy(int displayPolicy); + method public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setDynamicValueInvalidationFallback(androidx.wear.watchface.complications.data.WeightedElementsComplicationData? fallback); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setElementBackgroundColor(@ColorInt int elementBackgroundColor); + method @RequiresPermission("com.google.wear.permission.SET_COMPLICATION_EXTRAS") public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setExtras(android.os.PersistableBundle extras); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setPersistencePolicy(int persistencePolicy); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title); + method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange); + } + + public static final class WeightedElementsComplicationData.Companion { + method public int getMaxElements(); + property public java.util.List PLACEHOLDER; + property public androidx.wear.watchface.complications.data.ComplicationType TYPE; + } + + public static final class WeightedElementsComplicationData.Element { + ctor public WeightedElementsComplicationData.Element(@FloatRange(from=0.0, fromInclusive=false) float weight, @ColorInt int color); + method @InaccessibleFromKotlin public int getColor(); + method @InaccessibleFromKotlin public float getWeight(); + property public int color; + property public float weight; + } + +} + +package androidx.wear.watchface.complications.data.formatting { + + public final class ComplicationTextFormatting { + ctor public ComplicationTextFormatting(java.util.Locale locale); + method public String? getBestShortTextDateFormat(String[] skeletons, String? fallback); + method public String? getFormattedDayOfWeekForShortText(long timeInMillis, android.icu.util.TimeZone? timeZone); + method public String? getFormattedTimeForShortText(long timeInMillis, android.icu.util.TimeZone? timeZone, boolean use24Hour); + method @InaccessibleFromKotlin public String getFullTextDayOfWeekFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayOfMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextDayOfWeekFormat(); + method @InaccessibleFromKotlin public String? getShortTextMonthFormat(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat12Hour(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat12HourWithoutAmPmShortening(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat24Hour(); + method @InaccessibleFromKotlin public String? getShortTextTimeFormat24HourWithoutAmPmShortening(); + property public String fullTextDayOfWeekFormat; + property public String? shortTextDayMonthFormat; + property public String? shortTextDayOfMonthFormat; + property public String? shortTextDayOfWeekFormat; + property public String? shortTextMonthFormat; + property public String? shortTextTimeFormat12Hour; + property public String? shortTextTimeFormat12HourWithoutAmPmShortening; + property public String? shortTextTimeFormat24Hour; + property public String? shortTextTimeFormat24HourWithoutAmPmShortening; + } + +} + +package androidx.wear.watchface.complications.data.parser { + + public final class PreviewData { + method public operator androidx.wear.watchface.complications.data.ComplicationData? get(androidx.wear.watchface.complications.data.ComplicationType type); + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) @kotlin.jvm.Throws(exceptionClasses={XmlPullParserException::class, IOException::class}) public static androidx.wear.watchface.complications.data.parser.PreviewData inflate(android.content.Context providerContext, android.content.res.XmlResourceParser parser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + + public final class StaticPreviewDataParser { + method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static androidx.wear.watchface.complications.data.parser.PreviewData? parsePreviewData(android.content.Context context, android.content.ComponentName providerComponent); + field public static final androidx.wear.watchface.complications.data.parser.StaticPreviewDataParser INSTANCE; + } + +} + diff --git a/wear/watchface/watchface-complications-data/build.gradle b/wear/watchface/watchface-complications-data/build.gradle index cd790fb124660..28466c6542fc9 100644 --- a/wear/watchface/watchface-complications-data/build.gradle +++ b/wear/watchface/watchface-complications-data/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation("androidx.core:core:1.1.0") implementation("androidx.preference:preference:1.1.0") - implementation("androidx.wear.protolayout:protolayout-expression-pipeline:1.0.0-beta01") + implementation("androidx.wear.protolayout:protolayout-expression-pipeline:1.0.0") testImplementation(libs.testCore) testImplementation(libs.testRunner) testImplementation(libs.testRules) diff --git a/wear/watchface/watchface-complications/api/1.3.0-rc01.txt b/wear/watchface/watchface-complications/api/1.3.0-rc01.txt new file mode 100644 index 0000000000000..850af4a19b229 --- /dev/null +++ b/wear/watchface/watchface-complications/api/1.3.0-rc01.txt @@ -0,0 +1,117 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications { + + public final class ComplicationDataSourceInfo { + ctor public ComplicationDataSourceInfo(String appName, String name, android.graphics.drawable.Icon icon, androidx.wear.watchface.complications.data.ComplicationType type, android.content.ComponentName? componentName); + method @InaccessibleFromKotlin public String getAppName(); + method @InaccessibleFromKotlin public android.content.ComponentName? getComponentName(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getFallbackPreviewData(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getIcon(); + method @InaccessibleFromKotlin public String getName(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getType(); + property public String appName; + property public android.content.ComponentName? componentName; + property public androidx.wear.watchface.complications.data.ComplicationData fallbackPreviewData; + property public android.graphics.drawable.Icon icon; + property public String name; + property public androidx.wear.watchface.complications.data.ComplicationType type; + } + + public final class ComplicationDataSourceInfoRetriever implements java.lang.AutoCloseable { + ctor public ComplicationDataSourceInfoRetriever(android.content.Context context); + method public void close(); + method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException; + method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException; + } + + public static final class ComplicationDataSourceInfoRetriever.Result { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.ComplicationDataSourceInfo? getInfo(); + method @InaccessibleFromKotlin public int getSlotId(); + property public androidx.wear.watchface.complications.ComplicationDataSourceInfo? info; + property public int slotId; + } + + public static final class ComplicationDataSourceInfoRetriever.ServiceDisconnectedException extends java.lang.Exception { + ctor public ComplicationDataSourceInfoRetriever.ServiceDisconnectedException(); + } + + public final class ComplicationSlotBounds { + ctor public ComplicationSlotBounds(android.graphics.RectF bounds); + ctor public ComplicationSlotBounds(android.graphics.RectF bounds, optional android.graphics.RectF margins); + ctor @BytecodeOnly public ComplicationSlotBounds(android.graphics.RectF!, android.graphics.RectF!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + ctor @Deprecated public ComplicationSlotBounds(java.util.Map perComplicationTypeBounds); + ctor public ComplicationSlotBounds(java.util.Map perComplicationTypeBounds, java.util.Map perComplicationTypeMargins); + method @InaccessibleFromKotlin public java.util.Map getPerComplicationTypeBounds(); + method @InaccessibleFromKotlin public java.util.Map getPerComplicationTypeMargins(); + property public java.util.Map perComplicationTypeBounds; + property public java.util.Map perComplicationTypeMargins; + field public static final androidx.wear.watchface.complications.ComplicationSlotBounds.Companion Companion; + } + + public static final class ComplicationSlotBounds.Companion { + } + + public final class DefaultComplicationDataSourcePolicy { + ctor public DefaultComplicationDataSourcePolicy(); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, android.content.ComponentName secondaryDataSource, int systemDataSourceFallback); + ctor public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, androidx.wear.watchface.complications.data.ComplicationType primaryDataSourceDefaultType, android.content.ComponentName secondaryDataSource, androidx.wear.watchface.complications.data.ComplicationType secondaryDataSourceDefaultType, int systemDataSourceFallback, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType); + ctor public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, androidx.wear.watchface.complications.data.ComplicationType primaryDataSourceDefaultType, int systemDataSourceFallback, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(android.content.ComponentName dataSource, int systemDataSourceFallback); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(int systemProvider); + ctor public DefaultComplicationDataSourcePolicy(int systemDataSource, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceDefaultType); + method @InaccessibleFromKotlin public android.content.ComponentName? getPrimaryDataSource(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType? getPrimaryDataSourceDefaultType(); + method @InaccessibleFromKotlin public android.content.ComponentName? getSecondaryDataSource(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType? getSecondaryDataSourceDefaultType(); + method @InaccessibleFromKotlin public int getSystemDataSourceFallback(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getSystemDataSourceFallbackDefaultType(); + method public boolean isEmpty(); + property public android.content.ComponentName? primaryDataSource; + property public androidx.wear.watchface.complications.data.ComplicationType? primaryDataSourceDefaultType; + property public android.content.ComponentName? secondaryDataSource; + property public androidx.wear.watchface.complications.data.ComplicationType? secondaryDataSourceDefaultType; + property public int systemDataSourceFallback; + property public androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType; + field public static final androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy.Companion Companion; + } + + public static final class DefaultComplicationDataSourcePolicy.Companion { + } + + public final class SystemDataSources { + field public static final androidx.wear.watchface.complications.SystemDataSources.Companion Companion; + field public static final int DATA_SOURCE_APP_SHORTCUT = 6; // 0x6 + field public static final int DATA_SOURCE_DATE = 2; // 0x2 + field public static final int DATA_SOURCE_DAY_AND_DATE = 16; // 0x10 + field public static final int DATA_SOURCE_DAY_OF_WEEK = 13; // 0xd + field public static final int DATA_SOURCE_FAVORITE_CONTACT = 14; // 0xe + field @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final int DATA_SOURCE_HEART_RATE = 17; // 0x11 + field public static final int DATA_SOURCE_NEXT_EVENT = 9; // 0x9 + field public static final int DATA_SOURCE_STEP_COUNT = 4; // 0x4 + field public static final int DATA_SOURCE_SUNRISE_SUNSET = 12; // 0xc + field public static final int DATA_SOURCE_TIME_AND_DATE = 3; // 0x3 + field public static final int DATA_SOURCE_UNREAD_NOTIFICATION_COUNT = 7; // 0x7 + field public static final int DATA_SOURCE_WATCH_BATTERY = 1; // 0x1 + field public static final int DATA_SOURCE_WORLD_CLOCK = 5; // 0x5 + field public static final int NO_DATA_SOURCE = -1; // 0xffffffff + } + + public static final class SystemDataSources.Companion { + property public static int DATA_SOURCE_APP_SHORTCUT; + property public static int DATA_SOURCE_DATE; + property public static int DATA_SOURCE_DAY_AND_DATE; + property public static int DATA_SOURCE_DAY_OF_WEEK; + property public static int DATA_SOURCE_FAVORITE_CONTACT; + property public static int DATA_SOURCE_HEART_RATE; + property public static int DATA_SOURCE_NEXT_EVENT; + property public static int DATA_SOURCE_STEP_COUNT; + property public static int DATA_SOURCE_SUNRISE_SUNSET; + property public static int DATA_SOURCE_TIME_AND_DATE; + property public static int DATA_SOURCE_UNREAD_NOTIFICATION_COUNT; + property public static int DATA_SOURCE_WATCH_BATTERY; + property public static int DATA_SOURCE_WORLD_CLOCK; + property public static int NO_DATA_SOURCE; + } + +} + diff --git a/wear/watchface/watchface-complications/api/res-1.3.0-rc01.txt b/wear/watchface/watchface-complications/api/res-1.3.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/watchface/watchface-complications/api/restricted_1.3.0-rc01.txt b/wear/watchface/watchface-complications/api/restricted_1.3.0-rc01.txt new file mode 100644 index 0000000000000..850af4a19b229 --- /dev/null +++ b/wear/watchface/watchface-complications/api/restricted_1.3.0-rc01.txt @@ -0,0 +1,117 @@ +// Signature format: 4.0 +package androidx.wear.watchface.complications { + + public final class ComplicationDataSourceInfo { + ctor public ComplicationDataSourceInfo(String appName, String name, android.graphics.drawable.Icon icon, androidx.wear.watchface.complications.data.ComplicationType type, android.content.ComponentName? componentName); + method @InaccessibleFromKotlin public String getAppName(); + method @InaccessibleFromKotlin public android.content.ComponentName? getComponentName(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationData getFallbackPreviewData(); + method @InaccessibleFromKotlin public android.graphics.drawable.Icon getIcon(); + method @InaccessibleFromKotlin public String getName(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getType(); + property public String appName; + property public android.content.ComponentName? componentName; + property public androidx.wear.watchface.complications.data.ComplicationData fallbackPreviewData; + property public android.graphics.drawable.Icon icon; + property public String name; + property public androidx.wear.watchface.complications.data.ComplicationType type; + } + + public final class ComplicationDataSourceInfoRetriever implements java.lang.AutoCloseable { + ctor public ComplicationDataSourceInfoRetriever(android.content.Context context); + method public void close(); + method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException; + method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException; + } + + public static final class ComplicationDataSourceInfoRetriever.Result { + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.ComplicationDataSourceInfo? getInfo(); + method @InaccessibleFromKotlin public int getSlotId(); + property public androidx.wear.watchface.complications.ComplicationDataSourceInfo? info; + property public int slotId; + } + + public static final class ComplicationDataSourceInfoRetriever.ServiceDisconnectedException extends java.lang.Exception { + ctor public ComplicationDataSourceInfoRetriever.ServiceDisconnectedException(); + } + + public final class ComplicationSlotBounds { + ctor public ComplicationSlotBounds(android.graphics.RectF bounds); + ctor public ComplicationSlotBounds(android.graphics.RectF bounds, optional android.graphics.RectF margins); + ctor @BytecodeOnly public ComplicationSlotBounds(android.graphics.RectF!, android.graphics.RectF!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + ctor @Deprecated public ComplicationSlotBounds(java.util.Map perComplicationTypeBounds); + ctor public ComplicationSlotBounds(java.util.Map perComplicationTypeBounds, java.util.Map perComplicationTypeMargins); + method @InaccessibleFromKotlin public java.util.Map getPerComplicationTypeBounds(); + method @InaccessibleFromKotlin public java.util.Map getPerComplicationTypeMargins(); + property public java.util.Map perComplicationTypeBounds; + property public java.util.Map perComplicationTypeMargins; + field public static final androidx.wear.watchface.complications.ComplicationSlotBounds.Companion Companion; + } + + public static final class ComplicationSlotBounds.Companion { + } + + public final class DefaultComplicationDataSourcePolicy { + ctor public DefaultComplicationDataSourcePolicy(); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, android.content.ComponentName secondaryDataSource, int systemDataSourceFallback); + ctor public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, androidx.wear.watchface.complications.data.ComplicationType primaryDataSourceDefaultType, android.content.ComponentName secondaryDataSource, androidx.wear.watchface.complications.data.ComplicationType secondaryDataSourceDefaultType, int systemDataSourceFallback, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType); + ctor public DefaultComplicationDataSourcePolicy(android.content.ComponentName primaryDataSource, androidx.wear.watchface.complications.data.ComplicationType primaryDataSourceDefaultType, int systemDataSourceFallback, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(android.content.ComponentName dataSource, int systemDataSourceFallback); + ctor @Deprecated public DefaultComplicationDataSourcePolicy(int systemProvider); + ctor public DefaultComplicationDataSourcePolicy(int systemDataSource, androidx.wear.watchface.complications.data.ComplicationType systemDataSourceDefaultType); + method @InaccessibleFromKotlin public android.content.ComponentName? getPrimaryDataSource(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType? getPrimaryDataSourceDefaultType(); + method @InaccessibleFromKotlin public android.content.ComponentName? getSecondaryDataSource(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType? getSecondaryDataSourceDefaultType(); + method @InaccessibleFromKotlin public int getSystemDataSourceFallback(); + method @InaccessibleFromKotlin public androidx.wear.watchface.complications.data.ComplicationType getSystemDataSourceFallbackDefaultType(); + method public boolean isEmpty(); + property public android.content.ComponentName? primaryDataSource; + property public androidx.wear.watchface.complications.data.ComplicationType? primaryDataSourceDefaultType; + property public android.content.ComponentName? secondaryDataSource; + property public androidx.wear.watchface.complications.data.ComplicationType? secondaryDataSourceDefaultType; + property public int systemDataSourceFallback; + property public androidx.wear.watchface.complications.data.ComplicationType systemDataSourceFallbackDefaultType; + field public static final androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy.Companion Companion; + } + + public static final class DefaultComplicationDataSourcePolicy.Companion { + } + + public final class SystemDataSources { + field public static final androidx.wear.watchface.complications.SystemDataSources.Companion Companion; + field public static final int DATA_SOURCE_APP_SHORTCUT = 6; // 0x6 + field public static final int DATA_SOURCE_DATE = 2; // 0x2 + field public static final int DATA_SOURCE_DAY_AND_DATE = 16; // 0x10 + field public static final int DATA_SOURCE_DAY_OF_WEEK = 13; // 0xd + field public static final int DATA_SOURCE_FAVORITE_CONTACT = 14; // 0xe + field @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final int DATA_SOURCE_HEART_RATE = 17; // 0x11 + field public static final int DATA_SOURCE_NEXT_EVENT = 9; // 0x9 + field public static final int DATA_SOURCE_STEP_COUNT = 4; // 0x4 + field public static final int DATA_SOURCE_SUNRISE_SUNSET = 12; // 0xc + field public static final int DATA_SOURCE_TIME_AND_DATE = 3; // 0x3 + field public static final int DATA_SOURCE_UNREAD_NOTIFICATION_COUNT = 7; // 0x7 + field public static final int DATA_SOURCE_WATCH_BATTERY = 1; // 0x1 + field public static final int DATA_SOURCE_WORLD_CLOCK = 5; // 0x5 + field public static final int NO_DATA_SOURCE = -1; // 0xffffffff + } + + public static final class SystemDataSources.Companion { + property public static int DATA_SOURCE_APP_SHORTCUT; + property public static int DATA_SOURCE_DATE; + property public static int DATA_SOURCE_DAY_AND_DATE; + property public static int DATA_SOURCE_DAY_OF_WEEK; + property public static int DATA_SOURCE_FAVORITE_CONTACT; + property public static int DATA_SOURCE_HEART_RATE; + property public static int DATA_SOURCE_NEXT_EVENT; + property public static int DATA_SOURCE_STEP_COUNT; + property public static int DATA_SOURCE_SUNRISE_SUNSET; + property public static int DATA_SOURCE_TIME_AND_DATE; + property public static int DATA_SOURCE_UNREAD_NOTIFICATION_COUNT; + property public static int DATA_SOURCE_WATCH_BATTERY; + property public static int DATA_SOURCE_WORLD_CLOCK; + property public static int NO_DATA_SOURCE; + } + +} + diff --git a/wear/watchfacepush/watchfacepush/api/1.0.0-rc01.txt b/wear/watchfacepush/watchfacepush/api/1.0.0-rc01.txt new file mode 100644 index 0000000000000..f137e45bb2687 --- /dev/null +++ b/wear/watchfacepush/watchfacepush/api/1.0.0-rc01.txt @@ -0,0 +1,148 @@ +// Signature format: 4.0 +package androidx.wear.watchfacepush { + + public interface WatchFacePushManager { + method @kotlin.jvm.Throws(exceptionClasses=AddWatchFaceException::class) public suspend Object? addWatchFace(android.os.ParcelFileDescriptor apkFd, String validationToken, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.AddWatchFaceException; + method @kotlin.jvm.Throws(exceptionClasses=IsWatchFaceActiveException::class) public suspend Object? isWatchFaceActive(String watchfacePackageName, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.IsWatchFaceActiveException; + method @kotlin.jvm.Throws(exceptionClasses=ListWatchFacesException::class) public suspend Object? listWatchFaces(kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.ListWatchFacesException; + method @kotlin.jvm.Throws(exceptionClasses=RemoveWatchFaceException::class) public suspend Object? removeWatchFace(String slotId, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.RemoveWatchFaceException; + method @kotlin.jvm.Throws(exceptionClasses=SetWatchFaceAsActiveException::class) public suspend Object? setWatchFaceAsActive(String slotId, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.SetWatchFaceAsActiveException; + method @kotlin.jvm.Throws(exceptionClasses=UpdateWatchFaceException::class) public suspend Object? updateWatchFace(String slotId, android.os.ParcelFileDescriptor apkFd, String validationToken, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.UpdateWatchFaceException; + } + + public static final class WatchFacePushManager.AddWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.AddWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.AddWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 3; // 0x3 + field public static final int ERROR_INVALID_VALIDATION_TOKEN = 6; // 0x6 + field public static final int ERROR_MALFORMED_WATCHFACE_APK = 4; // 0x4 + field public static final int ERROR_SLOT_LIMIT_REACHED = 5; // 0x5 + field public static final int ERROR_UNEXPECTED_CONTENT = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.AddWatchFaceException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_INVALID_VALIDATION_TOKEN; + property public static int ERROR_MALFORMED_WATCHFACE_APK; + property public static int ERROR_SLOT_LIMIT_REACHED; + property public static int ERROR_UNEXPECTED_CONTENT; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.IsWatchFaceActiveException extends java.lang.Exception { + ctor public WatchFacePushManager.IsWatchFaceActiveException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.IsWatchFaceActiveException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.IsWatchFaceActiveException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.ListWatchFacesException extends java.lang.Exception { + ctor public WatchFacePushManager.ListWatchFacesException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.ListWatchFacesException.Companion Companion; + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.ListWatchFacesException.Companion { + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.ListWatchFacesResponse { + ctor public WatchFacePushManager.ListWatchFacesResponse(java.util.List installedWatchFaceDetails, int remainingSlotCount); + method @InaccessibleFromKotlin public java.util.List getInstalledWatchFaceDetails(); + method @InaccessibleFromKotlin public int getRemainingSlotCount(); + property public java.util.List installedWatchFaceDetails; + property public int remainingSlotCount; + } + + public static final class WatchFacePushManager.RemoveWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.RemoveWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.RemoveWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_SLOT_ID = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.RemoveWatchFaceException.Companion { + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.SetWatchFaceAsActiveException extends java.lang.Exception { + ctor public WatchFacePushManager.SetWatchFaceAsActiveException(int errorCode, optional Throwable? rootCause); + ctor @BytecodeOnly public WatchFacePushManager.SetWatchFaceAsActiveException(int, Throwable!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.SetWatchFaceAsActiveException.Companion Companion; + field public static final int ERROR_INVALID_SLOT_ID = 3; // 0x3 + field public static final int ERROR_MAXIMUM_ATTEMPTS_REACHED = 2; // 0x2 + field public static final int ERROR_MISSING_PERMISSION = 1000; // 0x3e8 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.SetWatchFaceAsActiveException.Companion { + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_MAXIMUM_ATTEMPTS_REACHED; + property public static int ERROR_MISSING_PERMISSION; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.UpdateWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.UpdateWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.UpdateWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 3; // 0x3 + field public static final int ERROR_INVALID_SLOT_ID = 5; // 0x5 + field public static final int ERROR_INVALID_VALIDATION_TOKEN = 6; // 0x6 + field public static final int ERROR_MALFORMED_WATCHFACE_APK = 4; // 0x4 + field public static final int ERROR_UNEXPECTED_CONTENT = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.UpdateWatchFaceException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_INVALID_VALIDATION_TOKEN; + property public static int ERROR_MALFORMED_WATCHFACE_APK; + property public static int ERROR_UNEXPECTED_CONTENT; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.WatchFaceDetails { + ctor public WatchFacePushManager.WatchFaceDetails(String slotId, long versionCode, String packageName, kotlin.jvm.functions.Function1> getMetaDataFunc); + method public kotlin.jvm.functions.Function0> getMetaData(String key); + method @InaccessibleFromKotlin public String getPackageName(); + method @InaccessibleFromKotlin public String getSlotId(); + method @InaccessibleFromKotlin public long getVersionCode(); + property public String packageName; + property public String slotId; + property public long versionCode; + } + + public final class WatchFacePushManagerFactory { + method public static androidx.wear.watchfacepush.WatchFacePushManager createWatchFacePushManager(android.content.Context context); + method public static boolean isSupported(); + field public static final androidx.wear.watchfacepush.WatchFacePushManagerFactory INSTANCE; + } + +} + diff --git a/wear/watchfacepush/watchfacepush/api/res-1.0.0-rc01.txt b/wear/watchfacepush/watchfacepush/api/res-1.0.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/watchfacepush/watchfacepush/api/restricted_1.0.0-rc01.txt b/wear/watchfacepush/watchfacepush/api/restricted_1.0.0-rc01.txt new file mode 100644 index 0000000000000..f137e45bb2687 --- /dev/null +++ b/wear/watchfacepush/watchfacepush/api/restricted_1.0.0-rc01.txt @@ -0,0 +1,148 @@ +// Signature format: 4.0 +package androidx.wear.watchfacepush { + + public interface WatchFacePushManager { + method @kotlin.jvm.Throws(exceptionClasses=AddWatchFaceException::class) public suspend Object? addWatchFace(android.os.ParcelFileDescriptor apkFd, String validationToken, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.AddWatchFaceException; + method @kotlin.jvm.Throws(exceptionClasses=IsWatchFaceActiveException::class) public suspend Object? isWatchFaceActive(String watchfacePackageName, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.IsWatchFaceActiveException; + method @kotlin.jvm.Throws(exceptionClasses=ListWatchFacesException::class) public suspend Object? listWatchFaces(kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.ListWatchFacesException; + method @kotlin.jvm.Throws(exceptionClasses=RemoveWatchFaceException::class) public suspend Object? removeWatchFace(String slotId, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.RemoveWatchFaceException; + method @kotlin.jvm.Throws(exceptionClasses=SetWatchFaceAsActiveException::class) public suspend Object? setWatchFaceAsActive(String slotId, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.SetWatchFaceAsActiveException; + method @kotlin.jvm.Throws(exceptionClasses=UpdateWatchFaceException::class) public suspend Object? updateWatchFace(String slotId, android.os.ParcelFileDescriptor apkFd, String validationToken, kotlin.coroutines.Continuation) throws androidx.wear.watchfacepush.WatchFacePushManager.UpdateWatchFaceException; + } + + public static final class WatchFacePushManager.AddWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.AddWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.AddWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 3; // 0x3 + field public static final int ERROR_INVALID_VALIDATION_TOKEN = 6; // 0x6 + field public static final int ERROR_MALFORMED_WATCHFACE_APK = 4; // 0x4 + field public static final int ERROR_SLOT_LIMIT_REACHED = 5; // 0x5 + field public static final int ERROR_UNEXPECTED_CONTENT = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.AddWatchFaceException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_INVALID_VALIDATION_TOKEN; + property public static int ERROR_MALFORMED_WATCHFACE_APK; + property public static int ERROR_SLOT_LIMIT_REACHED; + property public static int ERROR_UNEXPECTED_CONTENT; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.IsWatchFaceActiveException extends java.lang.Exception { + ctor public WatchFacePushManager.IsWatchFaceActiveException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.IsWatchFaceActiveException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.IsWatchFaceActiveException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.ListWatchFacesException extends java.lang.Exception { + ctor public WatchFacePushManager.ListWatchFacesException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.ListWatchFacesException.Companion Companion; + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.ListWatchFacesException.Companion { + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.ListWatchFacesResponse { + ctor public WatchFacePushManager.ListWatchFacesResponse(java.util.List installedWatchFaceDetails, int remainingSlotCount); + method @InaccessibleFromKotlin public java.util.List getInstalledWatchFaceDetails(); + method @InaccessibleFromKotlin public int getRemainingSlotCount(); + property public java.util.List installedWatchFaceDetails; + property public int remainingSlotCount; + } + + public static final class WatchFacePushManager.RemoveWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.RemoveWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.RemoveWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_SLOT_ID = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.RemoveWatchFaceException.Companion { + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.SetWatchFaceAsActiveException extends java.lang.Exception { + ctor public WatchFacePushManager.SetWatchFaceAsActiveException(int errorCode, optional Throwable? rootCause); + ctor @BytecodeOnly public WatchFacePushManager.SetWatchFaceAsActiveException(int, Throwable!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.SetWatchFaceAsActiveException.Companion Companion; + field public static final int ERROR_INVALID_SLOT_ID = 3; // 0x3 + field public static final int ERROR_MAXIMUM_ATTEMPTS_REACHED = 2; // 0x2 + field public static final int ERROR_MISSING_PERMISSION = 1000; // 0x3e8 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.SetWatchFaceAsActiveException.Companion { + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_MAXIMUM_ATTEMPTS_REACHED; + property public static int ERROR_MISSING_PERMISSION; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.UpdateWatchFaceException extends java.lang.Exception { + ctor public WatchFacePushManager.UpdateWatchFaceException(int errorCode, Throwable rootCause); + method @InaccessibleFromKotlin public int getErrorCode(); + property public int errorCode; + property public String? message; + field public static final androidx.wear.watchfacepush.WatchFacePushManager.UpdateWatchFaceException.Companion Companion; + field public static final int ERROR_INVALID_PACKAGE_NAME = 3; // 0x3 + field public static final int ERROR_INVALID_SLOT_ID = 5; // 0x5 + field public static final int ERROR_INVALID_VALIDATION_TOKEN = 6; // 0x6 + field public static final int ERROR_MALFORMED_WATCHFACE_APK = 4; // 0x4 + field public static final int ERROR_UNEXPECTED_CONTENT = 2; // 0x2 + field public static final int ERROR_UNKNOWN = 1; // 0x1 + } + + public static final class WatchFacePushManager.UpdateWatchFaceException.Companion { + property public static int ERROR_INVALID_PACKAGE_NAME; + property public static int ERROR_INVALID_SLOT_ID; + property public static int ERROR_INVALID_VALIDATION_TOKEN; + property public static int ERROR_MALFORMED_WATCHFACE_APK; + property public static int ERROR_UNEXPECTED_CONTENT; + property public static int ERROR_UNKNOWN; + } + + public static final class WatchFacePushManager.WatchFaceDetails { + ctor public WatchFacePushManager.WatchFaceDetails(String slotId, long versionCode, String packageName, kotlin.jvm.functions.Function1> getMetaDataFunc); + method public kotlin.jvm.functions.Function0> getMetaData(String key); + method @InaccessibleFromKotlin public String getPackageName(); + method @InaccessibleFromKotlin public String getSlotId(); + method @InaccessibleFromKotlin public long getVersionCode(); + property public String packageName; + property public String slotId; + property public long versionCode; + } + + public final class WatchFacePushManagerFactory { + method public static androidx.wear.watchfacepush.WatchFacePushManager createWatchFacePushManager(android.content.Context context); + method public static boolean isSupported(); + field public static final androidx.wear.watchfacepush.WatchFacePushManagerFactory INSTANCE; + } + +} + diff --git a/wear/wear-remote-interactions/api/1.2.0-rc01.txt b/wear/wear-remote-interactions/api/1.2.0-rc01.txt new file mode 100644 index 0000000000000..c2264e40a2a56 --- /dev/null +++ b/wear/wear-remote-interactions/api/1.2.0-rc01.txt @@ -0,0 +1,57 @@ +// Signature format: 4.0 +package androidx.wear.remote.interactions { + + public final class RemoteActivityHelper { + ctor public RemoteActivityHelper(android.content.Context context); + ctor public RemoteActivityHelper(android.content.Context context, optional java.util.concurrent.Executor executor); + ctor @BytecodeOnly public RemoteActivityHelper(android.content.Context!, java.util.concurrent.Executor!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + method @InaccessibleFromKotlin public kotlinx.coroutines.flow.Flow getAvailabilityStatus(); + method public static android.content.Intent? getTargetIntent(android.content.Intent intent); + method public static String? getTargetNodeId(android.content.Intent intent); + method public com.google.common.util.concurrent.ListenableFuture startRemoteActivity(android.content.Intent targetIntent); + method public com.google.common.util.concurrent.ListenableFuture startRemoteActivity(android.content.Intent targetIntent, optional String? targetNodeId); + method @BytecodeOnly public static com.google.common.util.concurrent.ListenableFuture! startRemoteActivity$default(androidx.wear.remote.interactions.RemoteActivityHelper!, android.content.Intent!, String!, int, Object!); + property public kotlinx.coroutines.flow.Flow availabilityStatus; + field public static final String ACTION_REMOTE_INTENT = "com.google.android.wearable.intent.action.REMOTE_INTENT"; + field public static final androidx.wear.remote.interactions.RemoteActivityHelper.Companion Companion; + field public static final int RESULT_FAILED = 1; // 0x1 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int STATUS_AVAILABLE = 3; // 0x3 + field public static final int STATUS_TEMPORARILY_UNAVAILABLE = 2; // 0x2 + field public static final int STATUS_UNAVAILABLE = 1; // 0x1 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + public static final class RemoteActivityHelper.Companion { + method public android.content.Intent? getTargetIntent(android.content.Intent intent); + method public String? getTargetNodeId(android.content.Intent intent); + property public static String ACTION_REMOTE_INTENT; + property public static int RESULT_FAILED; + property public static int RESULT_OK; + property public static int STATUS_AVAILABLE; + property public static int STATUS_TEMPORARILY_UNAVAILABLE; + property public static int STATUS_UNAVAILABLE; + property public static int STATUS_UNKNOWN; + } + + public static final class RemoteActivityHelper.RemoteIntentException extends java.lang.Exception { + ctor public RemoteActivityHelper.RemoteIntentException(String message); + } + + public final class WatchFaceConfigIntentHelper { + method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent); + method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent); + method public static android.content.Intent putPeerIdExtra(android.content.Intent watchFaceIntent, String peerId); + method public static android.content.Intent putWatchFaceComponentExtra(android.content.Intent watchFaceIntent, android.content.ComponentName componentName); + field public static final androidx.wear.remote.interactions.WatchFaceConfigIntentHelper.Companion Companion; + } + + public static final class WatchFaceConfigIntentHelper.Companion { + method public String? getPeerIdExtra(android.content.Intent watchFaceIntent); + method public android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent); + method public android.content.Intent putPeerIdExtra(android.content.Intent watchFaceIntent, String peerId); + method public android.content.Intent putWatchFaceComponentExtra(android.content.Intent watchFaceIntent, android.content.ComponentName componentName); + } + +} + diff --git a/wear/wear-remote-interactions/api/res-1.2.0-rc01.txt b/wear/wear-remote-interactions/api/res-1.2.0-rc01.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/wear/wear-remote-interactions/api/restricted_1.2.0-rc01.txt b/wear/wear-remote-interactions/api/restricted_1.2.0-rc01.txt new file mode 100644 index 0000000000000..c2264e40a2a56 --- /dev/null +++ b/wear/wear-remote-interactions/api/restricted_1.2.0-rc01.txt @@ -0,0 +1,57 @@ +// Signature format: 4.0 +package androidx.wear.remote.interactions { + + public final class RemoteActivityHelper { + ctor public RemoteActivityHelper(android.content.Context context); + ctor public RemoteActivityHelper(android.content.Context context, optional java.util.concurrent.Executor executor); + ctor @BytecodeOnly public RemoteActivityHelper(android.content.Context!, java.util.concurrent.Executor!, int, kotlin.jvm.internal.DefaultConstructorMarker!); + method @InaccessibleFromKotlin public kotlinx.coroutines.flow.Flow getAvailabilityStatus(); + method public static android.content.Intent? getTargetIntent(android.content.Intent intent); + method public static String? getTargetNodeId(android.content.Intent intent); + method public com.google.common.util.concurrent.ListenableFuture startRemoteActivity(android.content.Intent targetIntent); + method public com.google.common.util.concurrent.ListenableFuture startRemoteActivity(android.content.Intent targetIntent, optional String? targetNodeId); + method @BytecodeOnly public static com.google.common.util.concurrent.ListenableFuture! startRemoteActivity$default(androidx.wear.remote.interactions.RemoteActivityHelper!, android.content.Intent!, String!, int, Object!); + property public kotlinx.coroutines.flow.Flow availabilityStatus; + field public static final String ACTION_REMOTE_INTENT = "com.google.android.wearable.intent.action.REMOTE_INTENT"; + field public static final androidx.wear.remote.interactions.RemoteActivityHelper.Companion Companion; + field public static final int RESULT_FAILED = 1; // 0x1 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int STATUS_AVAILABLE = 3; // 0x3 + field public static final int STATUS_TEMPORARILY_UNAVAILABLE = 2; // 0x2 + field public static final int STATUS_UNAVAILABLE = 1; // 0x1 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + public static final class RemoteActivityHelper.Companion { + method public android.content.Intent? getTargetIntent(android.content.Intent intent); + method public String? getTargetNodeId(android.content.Intent intent); + property public static String ACTION_REMOTE_INTENT; + property public static int RESULT_FAILED; + property public static int RESULT_OK; + property public static int STATUS_AVAILABLE; + property public static int STATUS_TEMPORARILY_UNAVAILABLE; + property public static int STATUS_UNAVAILABLE; + property public static int STATUS_UNKNOWN; + } + + public static final class RemoteActivityHelper.RemoteIntentException extends java.lang.Exception { + ctor public RemoteActivityHelper.RemoteIntentException(String message); + } + + public final class WatchFaceConfigIntentHelper { + method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent); + method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent); + method public static android.content.Intent putPeerIdExtra(android.content.Intent watchFaceIntent, String peerId); + method public static android.content.Intent putWatchFaceComponentExtra(android.content.Intent watchFaceIntent, android.content.ComponentName componentName); + field public static final androidx.wear.remote.interactions.WatchFaceConfigIntentHelper.Companion Companion; + } + + public static final class WatchFaceConfigIntentHelper.Companion { + method public String? getPeerIdExtra(android.content.Intent watchFaceIntent); + method public android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent); + method public android.content.Intent putPeerIdExtra(android.content.Intent watchFaceIntent, String peerId); + method public android.content.Intent putWatchFaceComponentExtra(android.content.Intent watchFaceIntent, android.content.ComponentName componentName); + } + +} + diff --git a/wear/wear/api/1.4.0-rc01.txt b/wear/wear/api/1.4.0-rc01.txt new file mode 100644 index 0000000000000..20cee1f39df1d --- /dev/null +++ b/wear/wear/api/1.4.0-rc01.txt @@ -0,0 +1,492 @@ +// Signature format: 4.0 +package androidx.wear.activity { + + public class ConfirmationActivity extends android.app.Activity { + ctor public ConfirmationActivity(); + method protected void onAnimationFinished(); + method public void onCreate(android.os.Bundle!); + field public static final String EXTRA_ANIMATION_DURATION_MILLIS = "androidx.wear.activity.extra.ANIMATION_DURATION_MILLIS"; + field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE"; + field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE"; + field public static final int FAILURE_ANIMATION = 3; // 0x3 + field public static final int OPEN_ON_PHONE_ANIMATION = 2; // 0x2 + field public static final int SUCCESS_ANIMATION = 1; // 0x1 + } + +} + +package androidx.wear.ambient { + + public interface AmbientLifecycleObserver extends androidx.lifecycle.DefaultLifecycleObserver { + method @InaccessibleFromKotlin public boolean isAmbient(); + property public abstract boolean isAmbient; + } + + public static final class AmbientLifecycleObserver.AmbientDetails { + ctor public AmbientLifecycleObserver.AmbientDetails(boolean burnInProtectionRequired, boolean deviceHasLowBitAmbient); + method @InaccessibleFromKotlin public boolean getBurnInProtectionRequired(); + method @InaccessibleFromKotlin public boolean getDeviceHasLowBitAmbient(); + property public boolean burnInProtectionRequired; + property public boolean deviceHasLowBitAmbient; + } + + public static interface AmbientLifecycleObserver.AmbientLifecycleCallback { + method public default void onEnterAmbient(androidx.wear.ambient.AmbientLifecycleObserver.AmbientDetails ambientDetails); + method public default void onExitAmbient(); + method public default void onUpdateAmbient(); + } + + public final class AmbientLifecycleObserverKt { + method public static androidx.wear.ambient.AmbientLifecycleObserver AmbientLifecycleObserver(android.app.Activity activity, androidx.wear.ambient.AmbientLifecycleObserver.AmbientLifecycleCallback callbacks); + method public static androidx.wear.ambient.AmbientLifecycleObserver AmbientLifecycleObserver(android.app.Activity activity, java.util.concurrent.Executor callbackExecutor, androidx.wear.ambient.AmbientLifecycleObserver.AmbientLifecycleCallback callbacks); + } + + @Deprecated public final class AmbientMode extends android.app.Fragment { + ctor @Deprecated public AmbientMode(); + method @Deprecated public static androidx.wear.ambient.AmbientMode.AmbientController! attachAmbientSupport(T!); + method @Deprecated public void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String![]!); + method @Deprecated @CallSuper public void onAttach(android.content.Context!); + method @Deprecated @CallSuper public void onCreate(android.os.Bundle!); + method @Deprecated @CallSuper public void onDestroy(); + method @Deprecated @CallSuper public void onDetach(); + method @Deprecated @CallSuper public void onPause(); + method @Deprecated @CallSuper public void onResume(); + method @Deprecated @CallSuper public void onStop(); + field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION"; + field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT"; + field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode"; + } + + @Deprecated public abstract static class AmbientMode.AmbientCallback { + ctor @Deprecated public AmbientMode.AmbientCallback(); + method @Deprecated public void onAmbientOffloadInvalidated(); + method @Deprecated public void onEnterAmbient(android.os.Bundle!); + method @Deprecated public void onExitAmbient(); + method @Deprecated public void onUpdateAmbient(); + } + + @Deprecated public static interface AmbientMode.AmbientCallbackProvider { + method @Deprecated public androidx.wear.ambient.AmbientMode.AmbientCallback! getAmbientCallback(); + } + + @Deprecated public final class AmbientMode.AmbientController { + method @Deprecated public boolean isAmbient(); + method @Deprecated public void setAmbientOffloadEnabled(boolean); + } + + @Deprecated public final class AmbientModeSupport extends androidx.fragment.app.Fragment { + ctor @Deprecated public AmbientModeSupport(); + method @Deprecated public static androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!); + field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION"; + field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT"; + field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode"; + } + + @Deprecated public abstract static class AmbientModeSupport.AmbientCallback { + ctor @Deprecated public AmbientModeSupport.AmbientCallback(); + method @Deprecated public void onAmbientOffloadInvalidated(); + method @Deprecated public void onEnterAmbient(android.os.Bundle!); + method @Deprecated public void onExitAmbient(); + method @Deprecated public void onUpdateAmbient(); + } + + @Deprecated public static interface AmbientModeSupport.AmbientCallbackProvider { + method @Deprecated public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback(); + } + + @Deprecated public final class AmbientModeSupport.AmbientController { + method @Deprecated public boolean isAmbient(); + method @Deprecated public void setAmbientOffloadEnabled(boolean); + method @Deprecated public void setAutoResumeEnabled(boolean); + } + +} + +package androidx.wear.provider { + + public class WearableCalendarContract { + method public static void addCalendarAuthorityUri(android.content.UriMatcher, String, int); + method public static void addCalendarDataAuthority(android.content.IntentFilter, String?); + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Attendees { + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Instances { + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Reminders { + field public static final android.net.Uri CONTENT_URI; + } + +} + +package androidx.wear.utils { + + public class MetadataConstants { + method public static int getPreviewDrawableResourceId(android.content.Context!, boolean); + method public static boolean isNotificationBridgingEnabled(android.content.Context!); + method public static boolean isStandalone(android.content.Context!); + field public static final String NOTIFICATION_BRIDGE_MODE_BRIDGING = "BRIDGING"; + field public static final String NOTIFICATION_BRIDGE_MODE_METADATA_NAME = "com.google.android.wearable.notificationBridgeMode"; + field public static final String NOTIFICATION_BRIDGE_MODE_NO_BRIDGING = "NO_BRIDGING"; + field public static final String STANDALONE_METADATA_NAME = "com.google.android.wearable.standalone"; + field public static final String WATCH_FACE_PREVIEW_CIRCULAR_METADATA_NAME = "com.google.android.wearable.watchface.preview_circular"; + field public static final String WATCH_FACE_PREVIEW_METADATA_NAME = "com.google.android.wearable.watchface.preview"; + } + + public final class WearTypeHelper { + method public static boolean isChinaBuild(android.content.Context); + } + +} + +package androidx.wear.widget { + + @UiThread public class ArcLayout extends android.view.ViewGroup { + ctor public ArcLayout(android.content.Context); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?, int); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?, int, int); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getAnchorAngleDegrees(); + method public int getAnchorType(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMaxAngleDegrees(); + method public boolean isClockwise(); + method public void setAnchorAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setAnchorType(int); + method public void setClockwise(boolean); + method public void setMaxAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + field public static final int ANCHOR_CENTER = 1; // 0x1 + field public static final int ANCHOR_END = 2; // 0x2 + field public static final int ANCHOR_START = 0; // 0x0 + } + + public static class ArcLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams { + ctor public ArcLayout.LayoutParams(android.content.Context, android.util.AttributeSet?); + ctor public ArcLayout.LayoutParams(android.view.ViewGroup.LayoutParams); + ctor public ArcLayout.LayoutParams(int, int); + method public int getVerticalAlignment(); + method public float getWeight(); + method public boolean isRotated(); + method public void setRotated(boolean); + method public void setVerticalAlignment(int); + method public void setWeight(float); + field public static final int VERTICAL_ALIGN_CENTER = 1; // 0x1 + field public static final int VERTICAL_ALIGN_INNER = 2; // 0x2 + field public static final int VERTICAL_ALIGN_OUTER = 0; // 0x0 + } + + public static interface ArcLayout.Widget { + method public void checkInvalidAttributeAsChild(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getSweepAngleDegrees(); + method @Px public int getThickness(); + method public boolean isPointInsideClickArea(float, float); + method public default void setSweepAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + } + + @UiThread public class BoxInsetLayout extends android.view.ViewGroup { + ctor public BoxInsetLayout(android.content.Context); + ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet?); + ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet?, @StyleRes int); + method public androidx.wear.widget.BoxInsetLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!); + } + + public static class BoxInsetLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams { + ctor public BoxInsetLayout.LayoutParams(android.content.Context, android.util.AttributeSet?); + ctor public BoxInsetLayout.LayoutParams(android.view.ViewGroup.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams); + ctor public BoxInsetLayout.LayoutParams(android.widget.FrameLayout.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(androidx.wear.widget.BoxInsetLayout.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(int, int); + ctor public BoxInsetLayout.LayoutParams(int, int, int); + ctor public BoxInsetLayout.LayoutParams(int, int, int, int); + field public static final int BOX_ALL = 15; // 0xf + field public static final int BOX_BOTTOM = 8; // 0x8 + field public static final int BOX_LEFT = 1; // 0x1 + field public static final int BOX_NONE = 0; // 0x0 + field public static final int BOX_RIGHT = 4; // 0x4 + field public static final int BOX_TOP = 2; // 0x2 + field public int boxedEdges; + } + + public class CircularProgressLayout extends android.widget.FrameLayout { + ctor public CircularProgressLayout(android.content.Context!); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method @ColorInt public int getBackgroundColor(); + method public int[]! getColorSchemeColors(); + method public androidx.wear.widget.CircularProgressLayout.OnTimerFinishedListener? getOnTimerFinishedListener(); + method public androidx.swiperefreshlayout.widget.CircularProgressDrawable getProgressDrawable(); + method public float getStartingRotation(); + method public float getStrokeWidth(); + method public long getTotalTime(); + method public boolean isIndeterminate(); + method public boolean isTimerRunning(); + method public void setColorSchemeColors(int...!); + method public void setIndeterminate(boolean); + method public void setOnTimerFinishedListener(androidx.wear.widget.CircularProgressLayout.OnTimerFinishedListener?); + method public void setStartingRotation(float); + method public void setStrokeWidth(float); + method public void setTotalTime(long); + method public void startTimer(); + method public void stopTimer(); + } + + public static interface CircularProgressLayout.OnTimerFinishedListener { + method public void onTimerFinished(androidx.wear.widget.CircularProgressLayout!); + } + + public class ConfirmationOverlay { + ctor public ConfirmationOverlay(); + method public androidx.wear.widget.ConfirmationOverlay setDuration(int); + method @Deprecated public androidx.wear.widget.ConfirmationOverlay setFinishedAnimationListener(androidx.wear.widget.ConfirmationOverlay.OnAnimationFinishedListener?); + method public androidx.wear.widget.ConfirmationOverlay setMessage(CharSequence); + method @Deprecated public androidx.wear.widget.ConfirmationOverlay setMessage(String); + method public androidx.wear.widget.ConfirmationOverlay setOnAnimationFinishedListener(androidx.wear.widget.ConfirmationOverlay.OnAnimationFinishedListener?); + method public androidx.wear.widget.ConfirmationOverlay setType(@androidx.wear.widget.ConfirmationOverlay.OverlayType int); + method @MainThread public void showAbove(android.view.View); + method @MainThread public void showOn(android.app.Activity); + field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8 + field public static final int FAILURE_ANIMATION = 1; // 0x1 + field public static final int OPEN_ON_PHONE_ANIMATION = 2; // 0x2 + field public static final int SUCCESS_ANIMATION = 0; // 0x0 + } + + public static interface ConfirmationOverlay.OnAnimationFinishedListener { + method public void onAnimationFinished(); + } + + @IntDef({androidx.wear.widget.ConfirmationOverlay.SUCCESS_ANIMATION, androidx.wear.widget.ConfirmationOverlay.FAILURE_ANIMATION, androidx.wear.widget.ConfirmationOverlay.OPEN_ON_PHONE_ANIMATION}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ConfirmationOverlay.OverlayType { + } + + public class CurvedTextView extends android.view.View implements androidx.wear.widget.ArcLayout.Widget { + ctor public CurvedTextView(android.content.Context); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?, int); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?, int, int); + method public void checkInvalidAttributeAsChild(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getAnchorAngleDegrees(); + method public int getAnchorType(); + method public android.text.TextUtils.TruncateAt? getEllipsize(); + method public String? getFontFeatureSettings(); + method public String? getFontVariationSettings(); + method public float getLetterSpacing(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMaxSweepDegrees(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMinSweepDegrees(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getSweepAngleDegrees(); + method public String? getText(); + method @ColorInt public int getTextColor(); + method public float getTextSize(); + method @Px public int getThickness(); + method public android.graphics.Typeface? getTypeface(); + method public boolean isClockwise(); + method public boolean isPointInsideClickArea(float, float); + method public void setAnchorAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setAnchorType(int); + method public void setClockwise(boolean); + method public void setEllipsize(android.text.TextUtils.TruncateAt?); + method public void setFontFeatureSettings(String?); + method public void setFontVariationSettings(String?); + method public void setLetterSpacing(float); + method public void setSweepRangeDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float, @FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setText(String?); + method public void setTextColor(@ColorInt int); + method public void setTextSize(float); + method public void setTypeface(android.graphics.Typeface?); + method public void setTypeface(android.graphics.Typeface?, int); + } + + public class CurvingLayoutCallback extends androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback { + ctor public CurvingLayoutCallback(android.content.Context!); + method public void adjustAnchorOffsetXY(android.view.View!, float[]!); + method public void onLayoutFinished(android.view.View!, androidx.recyclerview.widget.RecyclerView!); + } + + @UiThread public class DismissibleFrameLayout extends android.widget.FrameLayout { + ctor public DismissibleFrameLayout(android.content.Context); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?, int); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?, int, int); + method public boolean isDismissableByBackButton(); + method public boolean isDismissableBySwipe(); + method protected void performDismissCanceledCallbacks(); + method protected void performDismissFinishedCallbacks(); + method protected void performDismissStartedCallbacks(); + method @UiThread public final void registerCallback(androidx.wear.widget.DismissibleFrameLayout.Callback); + method public final void setBackButtonDismissible(boolean); + method public final void setSwipeDismissible(boolean); + method @UiThread public final void unregisterCallback(androidx.wear.widget.DismissibleFrameLayout.Callback); + } + + @UiThread public abstract static class DismissibleFrameLayout.Callback { + ctor public DismissibleFrameLayout.Callback(); + method public void onDismissCanceled(androidx.wear.widget.DismissibleFrameLayout); + method public void onDismissFinished(androidx.wear.widget.DismissibleFrameLayout); + method public void onDismissStarted(androidx.wear.widget.DismissibleFrameLayout); + } + + public class RoundedDrawable extends android.graphics.drawable.Drawable { + ctor public RoundedDrawable(); + method public void draw(android.graphics.Canvas); + method @ColorInt public int getBackgroundColor(); + method public android.graphics.drawable.Drawable? getDrawable(); + method public int getOpacity(); + method public int getRadius(); + method public boolean isClipEnabled(); + method public void setAlpha(int); + method public void setBackgroundColor(@ColorInt int); + method public void setClipEnabled(boolean); + method public void setColorFilter(android.graphics.ColorFilter?); + method public void setDrawable(android.graphics.drawable.Drawable?); + method public void setRadius(int); + } + + @UiThread public class SwipeDismissFrameLayout extends androidx.wear.widget.DismissibleFrameLayout { + ctor public SwipeDismissFrameLayout(android.content.Context!); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method public void addCallback(androidx.wear.widget.SwipeDismissFrameLayout.Callback!); + method public float getDismissMinDragWidthRatio(); + method public boolean isSwipeable(); + method public void removeCallback(androidx.wear.widget.SwipeDismissFrameLayout.Callback!); + method public void setDismissMinDragWidthRatio(float); + method public void setSwipeable(boolean); + field public static final float DEFAULT_DISMISS_DRAG_WIDTH_RATIO = 0.33f; + } + + @UiThread public abstract static class SwipeDismissFrameLayout.Callback { + ctor public SwipeDismissFrameLayout.Callback(); + method public void onDismissed(androidx.wear.widget.SwipeDismissFrameLayout!); + method public void onSwipeCanceled(androidx.wear.widget.SwipeDismissFrameLayout!); + method public void onSwipeStarted(androidx.wear.widget.SwipeDismissFrameLayout!); + } + + public class WearableLinearLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager { + ctor public WearableLinearLayoutManager(android.content.Context!); + ctor public WearableLinearLayoutManager(android.content.Context!, androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback!); + method public androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback? getLayoutCallback(); + method public void setLayoutCallback(androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback?); + } + + public abstract static class WearableLinearLayoutManager.LayoutCallback { + ctor public WearableLinearLayoutManager.LayoutCallback(); + method public abstract void onLayoutFinished(android.view.View!, androidx.recyclerview.widget.RecyclerView!); + } + + public class WearableRecyclerView extends androidx.recyclerview.widget.RecyclerView { + ctor public WearableRecyclerView(android.content.Context!); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?, int); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?, int, int); + method public float getBezelFraction(); + method public float getScrollDegreesPerScreen(); + method public boolean isCircularScrollingGestureEnabled(); + method public boolean isEdgeItemsCenteringEnabled(); + method public void setBezelFraction(float); + method public void setCircularScrollingGestureEnabled(boolean); + method public void setEdgeItemsCenteringEnabled(boolean); + method public void setScrollDegreesPerScreen(float); + } + +} + +package androidx.wear.widget.drawer { + + public class WearableActionDrawerView extends androidx.wear.widget.drawer.WearableDrawerView { + ctor public WearableActionDrawerView(android.content.Context!); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public android.view.Menu! getMenu(); + method public void setOnMenuItemClickListener(android.view.MenuItem.OnMenuItemClickListener!); + method public void setTitle(CharSequence?); + } + + public class WearableDrawerController { + method public void closeDrawer(); + method public void openDrawer(); + method public void peekDrawer(); + } + + public class WearableDrawerLayout extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingParent android.view.View.OnLayoutChangeListener { + ctor public WearableDrawerLayout(android.content.Context!); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method public void onFlingComplete(android.view.View!); + method public void onLayoutChange(android.view.View!, int, int, int, int, int, int, int, int); + method public void setDrawerStateCallback(androidx.wear.widget.drawer.WearableDrawerLayout.DrawerStateCallback!); + } + + public static class WearableDrawerLayout.DrawerStateCallback { + ctor public WearableDrawerLayout.DrawerStateCallback(); + method public void onDrawerClosed(androidx.wear.widget.drawer.WearableDrawerLayout!, androidx.wear.widget.drawer.WearableDrawerView!); + method public void onDrawerOpened(androidx.wear.widget.drawer.WearableDrawerLayout!, androidx.wear.widget.drawer.WearableDrawerView!); + method public void onDrawerStateChanged(androidx.wear.widget.drawer.WearableDrawerLayout!, int); + } + + public class WearableDrawerView extends android.widget.FrameLayout { + ctor public WearableDrawerView(android.content.Context!); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public androidx.wear.widget.drawer.WearableDrawerController! getController(); + method public android.view.View? getDrawerContent(); + method public int getDrawerState(); + method public boolean isAutoPeekEnabled(); + method public boolean isClosed(); + method public boolean isLocked(); + method public boolean isLockedWhenClosed(); + method public boolean isOpenOnlyAtTopEnabled(); + method public boolean isOpened(); + method public boolean isPeekOnScrollDownEnabled(); + method public boolean isPeeking(); + method public void onDrawerClosed(); + method public void onDrawerOpened(); + method public void onDrawerStateChanged(int); + method public void onPeekContainerClicked(android.view.View!); + method public void setDrawerContent(android.view.View?); + method public void setIsAutoPeekEnabled(boolean); + method public void setIsLocked(boolean); + method public void setLockedWhenClosed(boolean); + method public void setOpenOnlyAtTopEnabled(boolean); + method public void setPeekContent(android.view.View!); + method public void setPeekOnScrollDownEnabled(boolean); + field public static final int STATE_DRAGGING = 1; // 0x1 + field public static final int STATE_IDLE = 0; // 0x0 + field public static final int STATE_SETTLING = 2; // 0x2 + } + + public class WearableNavigationDrawerView extends androidx.wear.widget.drawer.WearableDrawerView { + ctor public WearableNavigationDrawerView(android.content.Context!); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public void addOnItemSelectedListener(androidx.wear.widget.drawer.WearableNavigationDrawerView.OnItemSelectedListener!); + method public int getNavigationStyle(); + method public void removeOnItemSelectedListener(androidx.wear.widget.drawer.WearableNavigationDrawerView.OnItemSelectedListener!); + method public void setAdapter(androidx.wear.widget.drawer.WearableNavigationDrawerView.WearableNavigationDrawerAdapter!); + method public void setCurrentItem(int, boolean); + field public static final int MULTI_PAGE = 1; // 0x1 + field public static final int SINGLE_PAGE = 0; // 0x0 + } + + public static interface WearableNavigationDrawerView.OnItemSelectedListener { + method public void onItemSelected(int); + } + + public abstract static class WearableNavigationDrawerView.WearableNavigationDrawerAdapter { + ctor public WearableNavigationDrawerView.WearableNavigationDrawerAdapter(); + method public abstract int getCount(); + method public abstract android.graphics.drawable.Drawable! getItemDrawable(int); + method public abstract CharSequence! getItemText(int); + method public void notifyDataSetChanged(); + } + +} + diff --git a/wear/wear/api/res-1.4.0-rc01.txt b/wear/wear/api/res-1.4.0-rc01.txt new file mode 100644 index 0000000000000..44b0b55a8e564 --- /dev/null +++ b/wear/wear/api/res-1.4.0-rc01.txt @@ -0,0 +1 @@ +style Widget_Wear_RoundSwitch diff --git a/wear/wear/api/restricted_1.4.0-rc01.txt b/wear/wear/api/restricted_1.4.0-rc01.txt new file mode 100644 index 0000000000000..cf5a2b088e11a --- /dev/null +++ b/wear/wear/api/restricted_1.4.0-rc01.txt @@ -0,0 +1,499 @@ +// Signature format: 4.0 +package androidx.wear.activity { + + public class ConfirmationActivity extends android.app.Activity { + ctor public ConfirmationActivity(); + method protected void onAnimationFinished(); + method public void onCreate(android.os.Bundle!); + field public static final String EXTRA_ANIMATION_DURATION_MILLIS = "androidx.wear.activity.extra.ANIMATION_DURATION_MILLIS"; + field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE"; + field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE"; + field public static final int FAILURE_ANIMATION = 3; // 0x3 + field public static final int OPEN_ON_PHONE_ANIMATION = 2; // 0x2 + field public static final int SUCCESS_ANIMATION = 1; // 0x1 + } + +} + +package androidx.wear.ambient { + + public interface AmbientLifecycleObserver extends androidx.lifecycle.DefaultLifecycleObserver { + method @InaccessibleFromKotlin public boolean isAmbient(); + property public abstract boolean isAmbient; + } + + public static final class AmbientLifecycleObserver.AmbientDetails { + ctor public AmbientLifecycleObserver.AmbientDetails(boolean burnInProtectionRequired, boolean deviceHasLowBitAmbient); + method @InaccessibleFromKotlin public boolean getBurnInProtectionRequired(); + method @InaccessibleFromKotlin public boolean getDeviceHasLowBitAmbient(); + property public boolean burnInProtectionRequired; + property public boolean deviceHasLowBitAmbient; + } + + public static interface AmbientLifecycleObserver.AmbientLifecycleCallback { + method public default void onEnterAmbient(androidx.wear.ambient.AmbientLifecycleObserver.AmbientDetails ambientDetails); + method public default void onExitAmbient(); + method public default void onUpdateAmbient(); + } + + public final class AmbientLifecycleObserverKt { + method public static androidx.wear.ambient.AmbientLifecycleObserver AmbientLifecycleObserver(android.app.Activity activity, androidx.wear.ambient.AmbientLifecycleObserver.AmbientLifecycleCallback callbacks); + method public static androidx.wear.ambient.AmbientLifecycleObserver AmbientLifecycleObserver(android.app.Activity activity, java.util.concurrent.Executor callbackExecutor, androidx.wear.ambient.AmbientLifecycleObserver.AmbientLifecycleCallback callbacks); + } + + @Deprecated public final class AmbientMode extends android.app.Fragment { + ctor @Deprecated public AmbientMode(); + method @Deprecated public static androidx.wear.ambient.AmbientMode.AmbientController! attachAmbientSupport(T!); + method @Deprecated public void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String![]!); + method @Deprecated @CallSuper public void onAttach(android.content.Context!); + method @Deprecated @CallSuper public void onCreate(android.os.Bundle!); + method @Deprecated @CallSuper public void onDestroy(); + method @Deprecated @CallSuper public void onDetach(); + method @Deprecated @CallSuper public void onPause(); + method @Deprecated @CallSuper public void onResume(); + method @Deprecated @CallSuper public void onStop(); + field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION"; + field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT"; + field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode"; + } + + @Deprecated public abstract static class AmbientMode.AmbientCallback { + ctor @Deprecated public AmbientMode.AmbientCallback(); + method @Deprecated public void onAmbientOffloadInvalidated(); + method @Deprecated public void onEnterAmbient(android.os.Bundle!); + method @Deprecated public void onExitAmbient(); + method @Deprecated public void onUpdateAmbient(); + } + + @Deprecated public static interface AmbientMode.AmbientCallbackProvider { + method @Deprecated public androidx.wear.ambient.AmbientMode.AmbientCallback! getAmbientCallback(); + } + + @Deprecated public final class AmbientMode.AmbientController { + method @Deprecated public boolean isAmbient(); + method @Deprecated public void setAmbientOffloadEnabled(boolean); + } + + @Deprecated public final class AmbientModeSupport extends androidx.fragment.app.Fragment { + ctor @Deprecated public AmbientModeSupport(); + method @Deprecated public static androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!); + field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION"; + field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT"; + field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode"; + } + + @Deprecated public abstract static class AmbientModeSupport.AmbientCallback { + ctor @Deprecated public AmbientModeSupport.AmbientCallback(); + method @Deprecated public void onAmbientOffloadInvalidated(); + method @Deprecated public void onEnterAmbient(android.os.Bundle!); + method @Deprecated public void onExitAmbient(); + method @Deprecated public void onUpdateAmbient(); + } + + @Deprecated public static interface AmbientModeSupport.AmbientCallbackProvider { + method @Deprecated public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback(); + } + + @Deprecated public final class AmbientModeSupport.AmbientController { + method @Deprecated public boolean isAmbient(); + method @Deprecated public void setAmbientOffloadEnabled(boolean); + method @Deprecated public void setAutoResumeEnabled(boolean); + } + +} + +package androidx.wear.provider { + + public class WearableCalendarContract { + method public static void addCalendarAuthorityUri(android.content.UriMatcher, String, int); + method public static void addCalendarDataAuthority(android.content.IntentFilter, String?); + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Attendees { + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Instances { + field public static final android.net.Uri CONTENT_URI; + } + + public static final class WearableCalendarContract.Reminders { + field public static final android.net.Uri CONTENT_URI; + } + +} + +package androidx.wear.utils { + + public class MetadataConstants { + method public static int getPreviewDrawableResourceId(android.content.Context!, boolean); + method public static boolean isNotificationBridgingEnabled(android.content.Context!); + method public static boolean isStandalone(android.content.Context!); + field public static final String NOTIFICATION_BRIDGE_MODE_BRIDGING = "BRIDGING"; + field public static final String NOTIFICATION_BRIDGE_MODE_METADATA_NAME = "com.google.android.wearable.notificationBridgeMode"; + field public static final String NOTIFICATION_BRIDGE_MODE_NO_BRIDGING = "NO_BRIDGING"; + field public static final String STANDALONE_METADATA_NAME = "com.google.android.wearable.standalone"; + field public static final String WATCH_FACE_PREVIEW_CIRCULAR_METADATA_NAME = "com.google.android.wearable.watchface.preview_circular"; + field public static final String WATCH_FACE_PREVIEW_METADATA_NAME = "com.google.android.wearable.watchface.preview"; + } + + public final class WearTypeHelper { + method public static boolean isChinaBuild(android.content.Context); + } + +} + +package androidx.wear.widget { + + @UiThread public class ArcLayout extends android.view.ViewGroup { + ctor public ArcLayout(android.content.Context); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?, int); + ctor public ArcLayout(android.content.Context, android.util.AttributeSet?, int, int); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getAnchorAngleDegrees(); + method @androidx.wear.widget.ArcLayout.AnchorType public int getAnchorType(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMaxAngleDegrees(); + method public boolean isClockwise(); + method public void setAnchorAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setAnchorType(@androidx.wear.widget.ArcLayout.AnchorType int); + method public void setClockwise(boolean); + method public void setMaxAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + field public static final int ANCHOR_CENTER = 1; // 0x1 + field public static final int ANCHOR_END = 2; // 0x2 + field public static final int ANCHOR_START = 0; // 0x0 + } + + @IntDef({androidx.wear.widget.ArcLayout.ANCHOR_START, androidx.wear.widget.ArcLayout.ANCHOR_CENTER, androidx.wear.widget.ArcLayout.ANCHOR_END}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ArcLayout.AnchorType { + } + + public static class ArcLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams { + ctor public ArcLayout.LayoutParams(android.content.Context, android.util.AttributeSet?); + ctor public ArcLayout.LayoutParams(android.view.ViewGroup.LayoutParams); + ctor public ArcLayout.LayoutParams(int, int); + method @androidx.wear.widget.ArcLayout.LayoutParams.VerticalAlignment public int getVerticalAlignment(); + method public float getWeight(); + method public boolean isRotated(); + method public void setRotated(boolean); + method public void setVerticalAlignment(@androidx.wear.widget.ArcLayout.LayoutParams.VerticalAlignment int); + method public void setWeight(float); + field public static final int VERTICAL_ALIGN_CENTER = 1; // 0x1 + field public static final int VERTICAL_ALIGN_INNER = 2; // 0x2 + field public static final int VERTICAL_ALIGN_OUTER = 0; // 0x0 + } + + @IntDef({androidx.wear.widget.ArcLayout.LayoutParams.VERTICAL_ALIGN_OUTER, androidx.wear.widget.ArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER, androidx.wear.widget.ArcLayout.LayoutParams.VERTICAL_ALIGN_INNER}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ArcLayout.LayoutParams.VerticalAlignment { + } + + public static interface ArcLayout.Widget { + method public void checkInvalidAttributeAsChild(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getSweepAngleDegrees(); + method @Px public int getThickness(); + method public boolean isPointInsideClickArea(float, float); + method public default void setSweepAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + } + + @UiThread public class BoxInsetLayout extends android.view.ViewGroup { + ctor public BoxInsetLayout(android.content.Context); + ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet?); + ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet?, @StyleRes int); + method public androidx.wear.widget.BoxInsetLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!); + } + + public static class BoxInsetLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams { + ctor public BoxInsetLayout.LayoutParams(android.content.Context, android.util.AttributeSet?); + ctor public BoxInsetLayout.LayoutParams(android.view.ViewGroup.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams); + ctor public BoxInsetLayout.LayoutParams(android.widget.FrameLayout.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(androidx.wear.widget.BoxInsetLayout.LayoutParams); + ctor public BoxInsetLayout.LayoutParams(int, int); + ctor public BoxInsetLayout.LayoutParams(int, int, int); + ctor public BoxInsetLayout.LayoutParams(int, int, int, int); + field public static final int BOX_ALL = 15; // 0xf + field public static final int BOX_BOTTOM = 8; // 0x8 + field public static final int BOX_LEFT = 1; // 0x1 + field public static final int BOX_NONE = 0; // 0x0 + field public static final int BOX_RIGHT = 4; // 0x4 + field public static final int BOX_TOP = 2; // 0x2 + field public int boxedEdges; + } + + public class CircularProgressLayout extends android.widget.FrameLayout { + ctor public CircularProgressLayout(android.content.Context!); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public CircularProgressLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method @ColorInt public int getBackgroundColor(); + method public int[]! getColorSchemeColors(); + method public androidx.wear.widget.CircularProgressLayout.OnTimerFinishedListener? getOnTimerFinishedListener(); + method public androidx.swiperefreshlayout.widget.CircularProgressDrawable getProgressDrawable(); + method public float getStartingRotation(); + method public float getStrokeWidth(); + method public long getTotalTime(); + method public boolean isIndeterminate(); + method public boolean isTimerRunning(); + method public void setColorSchemeColors(int...!); + method public void setIndeterminate(boolean); + method public void setOnTimerFinishedListener(androidx.wear.widget.CircularProgressLayout.OnTimerFinishedListener?); + method public void setStartingRotation(float); + method public void setStrokeWidth(float); + method public void setTotalTime(long); + method public void startTimer(); + method public void stopTimer(); + } + + public static interface CircularProgressLayout.OnTimerFinishedListener { + method public void onTimerFinished(androidx.wear.widget.CircularProgressLayout!); + } + + public class ConfirmationOverlay { + ctor public ConfirmationOverlay(); + method @MainThread @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public void hide(); + method public androidx.wear.widget.ConfirmationOverlay setDuration(int); + method @Deprecated public androidx.wear.widget.ConfirmationOverlay setFinishedAnimationListener(androidx.wear.widget.ConfirmationOverlay.OnAnimationFinishedListener?); + method public androidx.wear.widget.ConfirmationOverlay setMessage(CharSequence); + method @Deprecated public androidx.wear.widget.ConfirmationOverlay setMessage(String); + method public androidx.wear.widget.ConfirmationOverlay setOnAnimationFinishedListener(androidx.wear.widget.ConfirmationOverlay.OnAnimationFinishedListener?); + method public androidx.wear.widget.ConfirmationOverlay setType(@androidx.wear.widget.ConfirmationOverlay.OverlayType int); + method @MainThread public void showAbove(android.view.View); + method @MainThread public void showOn(android.app.Activity); + field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8 + field public static final int FAILURE_ANIMATION = 1; // 0x1 + field public static final int OPEN_ON_PHONE_ANIMATION = 2; // 0x2 + field public static final int SUCCESS_ANIMATION = 0; // 0x0 + } + + public static interface ConfirmationOverlay.OnAnimationFinishedListener { + method public void onAnimationFinished(); + } + + @IntDef({androidx.wear.widget.ConfirmationOverlay.SUCCESS_ANIMATION, androidx.wear.widget.ConfirmationOverlay.FAILURE_ANIMATION, androidx.wear.widget.ConfirmationOverlay.OPEN_ON_PHONE_ANIMATION}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ConfirmationOverlay.OverlayType { + } + + public class CurvedTextView extends android.view.View implements androidx.wear.widget.ArcLayout.Widget { + ctor public CurvedTextView(android.content.Context); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?, int); + ctor public CurvedTextView(android.content.Context, android.util.AttributeSet?, int, int); + method public void checkInvalidAttributeAsChild(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getAnchorAngleDegrees(); + method @androidx.wear.widget.ArcLayout.AnchorType public int getAnchorType(); + method public android.text.TextUtils.TruncateAt? getEllipsize(); + method public String? getFontFeatureSettings(); + method public String? getFontVariationSettings(); + method public float getLetterSpacing(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMaxSweepDegrees(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getMinSweepDegrees(); + method @FloatRange(from=0.0f, to=360.0f, toInclusive=true) public float getSweepAngleDegrees(); + method public String? getText(); + method @ColorInt public int getTextColor(); + method public float getTextSize(); + method @Px public int getThickness(); + method public android.graphics.Typeface? getTypeface(); + method public boolean isClockwise(); + method public boolean isPointInsideClickArea(float, float); + method public void setAnchorAngleDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setAnchorType(@androidx.wear.widget.ArcLayout.AnchorType int); + method public void setClockwise(boolean); + method public void setEllipsize(android.text.TextUtils.TruncateAt?); + method public void setFontFeatureSettings(String?); + method public void setFontVariationSettings(String?); + method public void setLetterSpacing(float); + method public void setSweepRangeDegrees(@FloatRange(from=0.0f, to=360.0f, toInclusive=true) float, @FloatRange(from=0.0f, to=360.0f, toInclusive=true) float); + method public void setText(String?); + method public void setTextColor(@ColorInt int); + method public void setTextSize(float); + method public void setTypeface(android.graphics.Typeface?); + method public void setTypeface(android.graphics.Typeface?, int); + } + + public class CurvingLayoutCallback extends androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback { + ctor public CurvingLayoutCallback(android.content.Context!); + method public void adjustAnchorOffsetXY(android.view.View!, float[]!); + method public void onLayoutFinished(android.view.View!, androidx.recyclerview.widget.RecyclerView!); + } + + @UiThread public class DismissibleFrameLayout extends android.widget.FrameLayout { + ctor public DismissibleFrameLayout(android.content.Context); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?, int); + ctor public DismissibleFrameLayout(android.content.Context, android.util.AttributeSet?, int, int); + method public boolean isDismissableByBackButton(); + method public boolean isDismissableBySwipe(); + method protected void performDismissCanceledCallbacks(); + method protected void performDismissFinishedCallbacks(); + method protected void performDismissStartedCallbacks(); + method @UiThread public final void registerCallback(androidx.wear.widget.DismissibleFrameLayout.Callback); + method public final void setBackButtonDismissible(boolean); + method public final void setSwipeDismissible(boolean); + method @UiThread public final void unregisterCallback(androidx.wear.widget.DismissibleFrameLayout.Callback); + } + + @UiThread public abstract static class DismissibleFrameLayout.Callback { + ctor public DismissibleFrameLayout.Callback(); + method public void onDismissCanceled(androidx.wear.widget.DismissibleFrameLayout); + method public void onDismissFinished(androidx.wear.widget.DismissibleFrameLayout); + method public void onDismissStarted(androidx.wear.widget.DismissibleFrameLayout); + } + + public class RoundedDrawable extends android.graphics.drawable.Drawable { + ctor public RoundedDrawable(); + method public void draw(android.graphics.Canvas); + method @ColorInt public int getBackgroundColor(); + method public android.graphics.drawable.Drawable? getDrawable(); + method public int getOpacity(); + method public int getRadius(); + method public boolean isClipEnabled(); + method public void setAlpha(int); + method public void setBackgroundColor(@ColorInt int); + method public void setClipEnabled(boolean); + method public void setColorFilter(android.graphics.ColorFilter?); + method public void setDrawable(android.graphics.drawable.Drawable?); + method public void setRadius(int); + } + + @UiThread public class SwipeDismissFrameLayout extends androidx.wear.widget.DismissibleFrameLayout { + ctor public SwipeDismissFrameLayout(android.content.Context!); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public SwipeDismissFrameLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method public void addCallback(androidx.wear.widget.SwipeDismissFrameLayout.Callback!); + method public float getDismissMinDragWidthRatio(); + method public boolean isSwipeable(); + method public void removeCallback(androidx.wear.widget.SwipeDismissFrameLayout.Callback!); + method public void setDismissMinDragWidthRatio(float); + method public void setSwipeable(boolean); + field public static final float DEFAULT_DISMISS_DRAG_WIDTH_RATIO = 0.33f; + } + + @UiThread public abstract static class SwipeDismissFrameLayout.Callback { + ctor public SwipeDismissFrameLayout.Callback(); + method public void onDismissed(androidx.wear.widget.SwipeDismissFrameLayout!); + method public void onSwipeCanceled(androidx.wear.widget.SwipeDismissFrameLayout!); + method public void onSwipeStarted(androidx.wear.widget.SwipeDismissFrameLayout!); + } + + public class WearableLinearLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager { + ctor public WearableLinearLayoutManager(android.content.Context!); + ctor public WearableLinearLayoutManager(android.content.Context!, androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback!); + method public androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback? getLayoutCallback(); + method public void setLayoutCallback(androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback?); + } + + public abstract static class WearableLinearLayoutManager.LayoutCallback { + ctor public WearableLinearLayoutManager.LayoutCallback(); + method public abstract void onLayoutFinished(android.view.View!, androidx.recyclerview.widget.RecyclerView!); + } + + public class WearableRecyclerView extends androidx.recyclerview.widget.RecyclerView { + ctor public WearableRecyclerView(android.content.Context!); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?, int); + ctor public WearableRecyclerView(android.content.Context!, android.util.AttributeSet?, int, int); + method public float getBezelFraction(); + method public float getScrollDegreesPerScreen(); + method public boolean isCircularScrollingGestureEnabled(); + method public boolean isEdgeItemsCenteringEnabled(); + method public void setBezelFraction(float); + method public void setCircularScrollingGestureEnabled(boolean); + method public void setEdgeItemsCenteringEnabled(boolean); + method public void setScrollDegreesPerScreen(float); + } + +} + +package androidx.wear.widget.drawer { + + public class WearableActionDrawerView extends androidx.wear.widget.drawer.WearableDrawerView { + ctor public WearableActionDrawerView(android.content.Context!); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableActionDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public android.view.Menu! getMenu(); + method public void setOnMenuItemClickListener(android.view.MenuItem.OnMenuItemClickListener!); + method public void setTitle(CharSequence?); + } + + public class WearableDrawerController { + method public void closeDrawer(); + method public void openDrawer(); + method public void peekDrawer(); + } + + public class WearableDrawerLayout extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingParent android.view.View.OnLayoutChangeListener { + ctor public WearableDrawerLayout(android.content.Context!); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableDrawerLayout(android.content.Context!, android.util.AttributeSet!, int, int); + method public void onFlingComplete(android.view.View!); + method public void onLayoutChange(android.view.View!, int, int, int, int, int, int, int, int); + method public void setDrawerStateCallback(androidx.wear.widget.drawer.WearableDrawerLayout.DrawerStateCallback!); + } + + public static class WearableDrawerLayout.DrawerStateCallback { + ctor public WearableDrawerLayout.DrawerStateCallback(); + method public void onDrawerClosed(androidx.wear.widget.drawer.WearableDrawerLayout!, androidx.wear.widget.drawer.WearableDrawerView!); + method public void onDrawerOpened(androidx.wear.widget.drawer.WearableDrawerLayout!, androidx.wear.widget.drawer.WearableDrawerView!); + method public void onDrawerStateChanged(androidx.wear.widget.drawer.WearableDrawerLayout!, int); + } + + public class WearableDrawerView extends android.widget.FrameLayout { + ctor public WearableDrawerView(android.content.Context!); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public androidx.wear.widget.drawer.WearableDrawerController! getController(); + method public android.view.View? getDrawerContent(); + method public int getDrawerState(); + method public boolean isAutoPeekEnabled(); + method public boolean isClosed(); + method public boolean isLocked(); + method public boolean isLockedWhenClosed(); + method public boolean isOpenOnlyAtTopEnabled(); + method public boolean isOpened(); + method public boolean isPeekOnScrollDownEnabled(); + method public boolean isPeeking(); + method public void onDrawerClosed(); + method public void onDrawerOpened(); + method public void onDrawerStateChanged(int); + method public void onPeekContainerClicked(android.view.View!); + method public void setDrawerContent(android.view.View?); + method public void setIsAutoPeekEnabled(boolean); + method public void setIsLocked(boolean); + method public void setLockedWhenClosed(boolean); + method public void setOpenOnlyAtTopEnabled(boolean); + method public void setPeekContent(android.view.View!); + method public void setPeekOnScrollDownEnabled(boolean); + field public static final int STATE_DRAGGING = 1; // 0x1 + field public static final int STATE_IDLE = 0; // 0x0 + field public static final int STATE_SETTLING = 2; // 0x2 + } + + public class WearableNavigationDrawerView extends androidx.wear.widget.drawer.WearableDrawerView { + ctor public WearableNavigationDrawerView(android.content.Context!); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!, int); + ctor public WearableNavigationDrawerView(android.content.Context!, android.util.AttributeSet!, int, int); + method public void addOnItemSelectedListener(androidx.wear.widget.drawer.WearableNavigationDrawerView.OnItemSelectedListener!); + method public int getNavigationStyle(); + method public void removeOnItemSelectedListener(androidx.wear.widget.drawer.WearableNavigationDrawerView.OnItemSelectedListener!); + method public void setAdapter(androidx.wear.widget.drawer.WearableNavigationDrawerView.WearableNavigationDrawerAdapter!); + method public void setCurrentItem(int, boolean); + field public static final int MULTI_PAGE = 1; // 0x1 + field public static final int SINGLE_PAGE = 0; // 0x0 + } + + public static interface WearableNavigationDrawerView.OnItemSelectedListener { + method public void onItemSelected(int); + } + + public abstract static class WearableNavigationDrawerView.WearableNavigationDrawerAdapter { + ctor public WearableNavigationDrawerView.WearableNavigationDrawerAdapter(); + method public abstract int getCount(); + method public abstract android.graphics.drawable.Drawable! getItemDrawable(int); + method public abstract CharSequence! getItemText(int); + method public void notifyDataSetChanged(); + } + +} + diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewBuilderTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewBuilderTest.java index ca70f303e9948..f23c6aa80e61b 100644 --- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewBuilderTest.java +++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewBuilderTest.java @@ -16,26 +16,36 @@ package androidx.webkit; +import android.content.Context; import android.os.Build; +import android.os.Bundle; +import android.view.Choreographer; +import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.WebView; -import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; +import androidx.webkit.instrumentation.test.R; import androidx.webkit.test.common.TestWebMessageListener; import androidx.webkit.test.common.WebViewOnUiThread; import androidx.webkit.test.common.WebkitUtils; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import okhttp3.HttpUrl; import okhttp3.mockwebserver.MockResponse; @@ -45,6 +55,10 @@ @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class WebViewBuilderTest { + @Rule + public final ActivityScenarioRule mActivityScenarioRule = + new ActivityScenarioRule<>(WebViewTestActivity.class); + @Before public void setUp() { WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V1); @@ -54,45 +68,295 @@ public void setUp() { public void testConstructsWebView() { WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); - try (ActivityScenario scenario = - ActivityScenario.launch(WebViewTestActivity.class)) { - scenario.onActivity( - activity -> { - try { - WebView webView = builder.build(activity); - Assert.assertNotNull(webView); - Assert.assertTrue(webView instanceof WebView); - - // We then destroy the WebView to avoid leaking into GC tests. - webView.destroy(); - } catch (WebViewBuilderException e) { - Assert.fail(e.toString()); - } - }); - } + mActivityScenarioRule.getScenario().onActivity(activity -> { + try { + WebView webView = builder.build(activity); + Assert.assertNotNull(webView); + Assert.assertTrue(webView instanceof WebView); + + // We then destroy the WebView to avoid leaking into GC tests. + webView.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + }); } @Test public void testConstructsWebViewTwice() { WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); - try (ActivityScenario scenario = - ActivityScenario.launch(WebViewTestActivity.class)) { - scenario.onActivity( - activity -> { - try { - WebView webView = builder.build(activity); - WebView webView2 = builder.build(activity); - // These were two different WebView objects created. - Assert.assertTrue(webView != webView2); - - // We then destroy these WebViews to avoid leaking into GC tests. - webView.destroy(); - webView2.destroy(); - } catch (WebViewBuilderException e) { - Assert.fail(e.toString()); - } - }); + mActivityScenarioRule.getScenario().onActivity(activity -> { + try { + WebView webView = builder.build(activity); + WebView webView2 = builder.build(activity); + // These were two different WebView objects created. + Assert.assertTrue(webView != webView2); + + // We then destroy these WebViews to avoid leaking into GC tests. + webView.destroy(); + webView2.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + }); + } + + @Test + public void testApplyToUnusedWebView_succeedsOnce() { + WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2); + + WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); + WebViewBuilder builder2 = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); + + mActivityScenarioRule.getScenario().onActivity(activity -> { + WebView webView = new WebView(activity); + try { + WebView outWebView = builder.applyTo(webView); + // The argument should be returned as is. + Assert.assertSame(webView, outWebView); + + // Only one applyTo call per WebView is allowed, regardless of builder. + Assert.assertThrows(IllegalStateException.class, () -> { + builder.applyTo(webView); + }); + Assert.assertThrows(IllegalStateException.class, () -> { + builder2.applyTo(webView); + }); + + // We then destroy the WebView to avoid leaking into GC tests. + webView.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + }); + } + + /** + * Asserts that IllegalStateException is thrown when calling + * {@link WebViewBuilder#applyTo(WebView)} on a WebView that has first had a given action + * performed on it. + */ + private void assertBuilderApplicationThrowsForUsedWebView(Consumer action) { + WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2); + WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); + mActivityScenarioRule.getScenario().onActivity(activity -> { + WebView webView = new WebView(activity); + action.accept(webView); + try { + Assert.assertThrows(IllegalStateException.class, + () -> builder.applyTo(webView)); + + // We then destroy the WebView to avoid leaking into GC tests. + webView.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + }); + } + + // testApplyToUsedWebView_* test a non-exhaustive list of interesting APIs which should (or + // should not) taint the WebView such that the builder cannot be applied. Some of the notable + // things these tests cover include: + // - Framework and SupportLib APIs + // - APIs which do and don't go through AwContents. + // - Getters and setters. + // - Default and non-default profiles. + // - Navigation. + // - JavaScript interfaces. + // - JavaScript evaluations. + @Test + public void testApplyToUsedWebView_getSettings_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::getSettings); + } + + @Test + // AndroidLintNewApi and ConstantValue lints fight over whether there should be an if statement. + @SuppressWarnings("AndroidLintNewApi") + public void testApplyToUsedWebView_getWebViewClient_illegalStateException() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O); + // Note that we specifically want to test the framework method here, not the compat one. + // The compat version is tested separately. + assertBuilderApplicationThrowsForUsedWebView(WebView::getWebViewClient); + } + + @Test + public void testApplyToUsedWebView_getUrl_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::getUrl); + } + + @Test + public void testApplyToUsedWebView_loadUrl_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView((webview) -> webview.loadUrl("about:blank")); + } + + @Test + public void testApplyToUsedWebView_saveState_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView((webview) -> webview.saveState(new Bundle())); + } + + @Test + public void testApplyToUsedWebView_restoreState_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> webview.restoreState(new Bundle())); + } + + @Test + public void testApplyToUsedWebView_addJavascriptInterface_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> webview.addJavascriptInterface(new Object(), "justTesting")); + } + + @Test + public void testApplyToUsedWebView_evaluateJavascript_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> webview.evaluateJavascript("1", null)); + } + + @Test + public void testApplyToUsedWebView_setAcceptThirdPartyCookies_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> CookieManager.getInstance().setAcceptThirdPartyCookies(webview, true)); + } + + @Test + public void testApplyToUsedWebView_compatGetWebViewClient_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_CLIENT); + assertBuilderApplicationThrowsForUsedWebView(WebViewCompat::getWebViewClient); + } + + @Test + public void testApplyToUsedWebView_getProfile_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.MULTI_PROFILE); + assertBuilderApplicationThrowsForUsedWebView(WebViewCompat::getProfile); + } + + @Test + public void testApplyToUsedWebView_setDefaultProfile_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.MULTI_PROFILE); + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> WebViewCompat.setProfile(webview, Profile.DEFAULT_PROFILE_NAME)); + } + + @Test + public void testApplyToUsedWebView_setNonDefaultProfile_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.MULTI_PROFILE); + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> WebViewCompat.setProfile(webview, "NonDefault")); + } + + @Test + public void testApplyToUsedWebView_compatSaveState_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.SAVE_STATE); + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> WebViewCompat.saveState(webview, new Bundle(), 1000000, true)); + } + + @Test + public void testApplyToUsedWebView_addDocumentStartJavaScript_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.DOCUMENT_START_SCRIPT); + assertBuilderApplicationThrowsForUsedWebView( + (webview) -> WebViewCompat.addDocumentStartJavaScript(webview, "1", Set.of("*"))); + } + + @Test + public void testApplyToUsedWebView_isAudioMuted_illegalStateException() { + WebkitUtils.checkFeature(WebViewFeature.MUTE_AUDIO); + assertBuilderApplicationThrowsForUsedWebView(WebViewCompat::isAudioMuted); + } + + @Test + public void testApplyToUsedWebView_onPause_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::onPause); + } + + @Test + public void testApplyToUsedWebView_onResume_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::onResume); + } + + @Test + public void testApplyToUsedWebView_pauseTimers_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::pauseTimers); + } + + @Test + public void testApplyToUsedWebView_resumeTimers_illegalStateException() { + assertBuilderApplicationThrowsForUsedWebView(WebView::resumeTimers); + } + + @Test + public void testApplyToSubclassedWebView() { + WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2); + + WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); + + class WebViewSubclass extends WebView { + WebViewSubclass(Context context) { + super(context); + } + } + + mActivityScenarioRule.getScenario().onActivity(activity -> { + WebViewSubclass webView = new WebViewSubclass(activity); + try { + WebViewSubclass outWebView = builder.applyTo(webView); + // The argument should be returned as is. + Assert.assertSame(webView, outWebView); + + // We then destroy the WebView to avoid leaking into GC tests. + webView.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + }); + } + + @Test + public void testApplyToInflatedWebView() throws Exception { + WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2); + + WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mActivityScenarioRule.getScenario().onActivity(activity -> { + // Inflate immediately. + activity.setContentView(R.layout.inflated_webview); + WebView webView = activity.findViewById(R.id.inflated_webview); + + // Wait a few frames for any natural View (etc.) interactions to happen, then call + // applyTo. Note that this may not cover everything, e.g. input-related interactions. + Choreographer.FrameCallback callback = new Choreographer.FrameCallback() { + private int mRemainingFrames = 5; + + @Override + public void doFrame(long frameTimeNanos) { + if (mRemainingFrames > 0) { + mRemainingFrames--; + Choreographer.getInstance().postFrameCallback(this); + return; + } + + try { + WebView outWebView = builder.applyTo(webView); + // The argument should be returned as is. + Assert.assertSame(webView, outWebView); + + // We then destroy the WebView to avoid leaking into GC tests. + webView.destroy(); + } catch (WebViewBuilderException e) { + throw new AssertionError(e); + } + countDownLatch.countDown(); + } + }; + + Choreographer.getInstance().postFrameCallback(callback); + }); + + if (!countDownLatch.await(5, TimeUnit.SECONDS)) { + Assert.fail("Timed out waiting for applyTo to complete."); } } @@ -230,6 +494,25 @@ public void testSetProfileName() throws WebViewBuilderException { Assert.assertEquals(profileName, actualProfileName); } + @Test + public void testSetProfileNameWithApplyTo() throws WebViewBuilderException { + WebkitUtils.checkFeature(WebViewFeature.MULTI_PROFILE); + WebkitUtils.checkFeature(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2); + + String profileName = "NonDefault"; + WebViewBuilder builder = new WebViewBuilder(WebViewBuilder.PRESET_LEGACY) + .setProfile(profileName); + String actualProfileName = WebkitUtils.onMainThreadSync(() -> { + WebView webView = + builder.applyTo(new WebView(ApplicationProvider.getApplicationContext())); + String result = WebViewCompat.getProfile(webView).getName(); + webView.destroy(); + return result; + }); + + Assert.assertEquals(profileName, actualProfileName); + } + private WebView build(final WebViewBuilder builder) throws WebViewBuilderException { return WebkitUtils.onMainThreadSync( () -> { diff --git a/webkit/integration-tests/instrumentation/src/androidTest/res/layout/inflated_webview.xml b/webkit/integration-tests/instrumentation/src/androidTest/res/layout/inflated_webview.xml new file mode 100644 index 0000000000000..4711d73fb5614 --- /dev/null +++ b/webkit/integration-tests/instrumentation/src/androidTest/res/layout/inflated_webview.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewBuilder.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewBuilder.java index 6ad7779a36eb7..2be3160f3947f 100644 --- a/webkit/webkit/src/main/java/androidx/webkit/WebViewBuilder.java +++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewBuilder.java @@ -161,14 +161,73 @@ public WebViewBuilder(@Preset int preset) { throw WebViewFeatureInternal.getUnsupportedOperationException(); } + WebViewBuilderBoundaryInterface builder = getBuilderStateBoundary(); + // makeConfig must be called every time in case the builder state changes. + WebViewBuilderBoundaryInterface.Config config = makeConfig(); + + try { + return builder.build(context, config); + } catch (RuntimeException e) { + throw new WebViewBuilderException(e); + } + } + + /** + * Applies a builder config to an existing but unused WebView. + * + *

This allows the builder to be used in cases where {@link WebViewBuilder#build(Context)} is + * not practical, including cases where WebView has been inflated from an XML layout or + * subclassed. + * + *

It is not permitted to call any other WebView APIs on the WebView before this. A WebView + * may only have a builder configuration applied at most once. This API may not be used with + * WebViews that were built with {@link WebViewBuilder#build(Context)}. + * + * @param webview The WebView to apply the config to. + * @throws WebViewBuilderException if there was an issue with validation or constructing the + * WebView. + * @throws IllegalStateException if the WebView has already been used or configured in some way. + */ + @UiThread + @RequiresFeature( + name = WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2, + enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported") + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public @NonNull T applyTo(@NonNull T webview) { + final ApiFeature.NoFramework feature = WebViewFeatureInternal.WEBVIEW_BUILDER_V2; + if (!feature.isSupportedByWebView()) { + throw WebViewFeatureInternal.getUnsupportedOperationException(); + } + + WebViewBuilderBoundaryInterface builder = getBuilderStateBoundary(); + // makeConfig must be called every time in case the builder state changes. + WebViewBuilderBoundaryInterface.Config config = makeConfig(); + + try { + builder.applyTo(webview, config); + } catch (IllegalStateException e) { + // Special case IllegalStateException from any other RuntimeExceptions handled below. + // IllegalStateException probably indicates we were passed a bad WebView, rather than a + // bad config, so simply rethrow it. + throw e; + } catch (RuntimeException e) { + throw new WebViewBuilderException(e); + } + + return webview; + } + + private @NonNull WebViewBuilderBoundaryInterface getBuilderStateBoundary() { // The boundary interface is lazy loaded but it is built with the // assumption that on every call to build, we can re-use the same instance. - // Configure and build must be called every time in case the - // builder state changes. if (mBuilderStateBoundary == null) { mBuilderStateBoundary = WebViewGlueCommunicator.getFactory().getWebViewBuilder(); } + return mBuilderStateBoundary; + } + + private WebViewBuilderBoundaryInterface.@NonNull Config makeConfig() { WebViewBuilderBoundaryInterface.Config config = new WebViewBuilderBoundaryInterface.Config(); @@ -179,10 +238,10 @@ public WebViewBuilder(@Preset int preset) { for (RestrictionAllowlist allowList : mAllowLists) { allowList.configure(config); } - - return mBuilderStateBoundary.build(context, config); } catch (RuntimeException e) { throw new WebViewBuilderException(e); } + + return config; } } diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java index 87e3d7407936a..91e92cd433d63 100644 --- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java +++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java @@ -126,6 +126,7 @@ private WebViewFeature() { NAVIGATION_LISTENER_V1, PAYMENT_REQUEST, WEBVIEW_BUILDER_EXPERIMENTAL_V1, + WEBVIEW_BUILDER_EXPERIMENTAL_V2, WARM_UP_RENDERER_PROCESS, PRECONNECT, PROVIDER_WEAKLY_REF_WEBVIEW, @@ -754,6 +755,16 @@ private WebViewFeature() { @WebViewBuilder.Experimental public static final String WEBVIEW_BUILDER_EXPERIMENTAL_V1 = "WEBVIEW_BUILDER_EXPERIMENTAL_V1"; + /** + * Feature for {@link #isFeatureSupported(String)}. + * This feature covers: + * {@link WebViewBuilder#applyTo(WebView)}. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @WebViewBuilder.Experimental + public static final String WEBVIEW_BUILDER_EXPERIMENTAL_V2 = + "WEBVIEW_BUILDER_EXPERIMENTAL_V2"; + /** * Feature for {@link #isFeatureSupported(String)}. * This feature covers diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java index a61825a1f1db5..717c6eab2144e 100644 --- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java +++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java @@ -837,6 +837,16 @@ public boolean isSupportedByWebView() { new ApiFeature.NoFramework(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V1, Features.WEBVIEW_BUILDER); + /** + * Feature for {@link WebViewFeature#isFeatureSupported(String)}. + * This feature covers: + * {@link WebViewBuilder#applyTo(WebView)}. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public static final ApiFeature.NoFramework WEBVIEW_BUILDER_V2 = + new ApiFeature.NoFramework(WebViewFeature.WEBVIEW_BUILDER_EXPERIMENTAL_V2, + Features.WEBVIEW_BUILDER_V2); + /** * Feature for {@link WebViewFeature#isFeatureSupported(String)}. * This feature covers diff --git a/xr/arcore/integration-tests/whitebox-mobile/build.gradle b/xr/arcore/integration-tests/whitebox-mobile/build.gradle index d345f541f78ff..3a31fbb4ab94a 100644 --- a/xr/arcore/integration-tests/whitebox-mobile/build.gradle +++ b/xr/arcore/integration-tests/whitebox-mobile/build.gradle @@ -74,6 +74,7 @@ dependencies { implementation("androidx.compose.ui:ui:1.7.5") implementation("androidx.graphics:graphics-core:1.0.3") implementation("androidx.lifecycle:lifecycle-runtime:2.8.7") + implementation("androidx.window:window:1.1.0") implementation("com.google.ar:core:1.49.0") implementation("de.javagl:obj:0.4.0") diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/AndroidManifest.xml b/xr/arcore/integration-tests/whitebox-mobile/src/main/AndroidManifest.xml index 5e8dd866be16a..f0d50f99c0f8b 100644 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/AndroidManifest.xml +++ b/xr/arcore/integration-tests/whitebox-mobile/src/main/AndroidManifest.xml @@ -35,17 +35,7 @@ - - - - diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/MainActivity.kt b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/MainActivity.kt index fe4234ebbbe25..bcdf90fafec87 100644 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/MainActivity.kt +++ b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/MainActivity.kt @@ -37,12 +37,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.xr.arcore.apps.whitebox.mobile.anchors.AnchorsActivity +import androidx.xr.arcore.apps.whitebox.mobile.anchorsplaneshittest.AnchorsPlanesHitTestActivity import androidx.xr.arcore.apps.whitebox.mobile.depthmaps.DepthMapsActivity import androidx.xr.arcore.apps.whitebox.mobile.facemeshing.FaceMeshActivity import androidx.xr.arcore.apps.whitebox.mobile.geospatial.GeospatialActivity -import androidx.xr.arcore.apps.whitebox.mobile.hittest.HitTestActivity -import androidx.xr.arcore.apps.whitebox.mobile.planes.PlanesActivity import androidx.xr.runtime.Log import java.text.SimpleDateFormat import java.util.Locale @@ -102,19 +100,11 @@ fun WhiteboxSessionMenu() { ) HorizontalDivider() TextButton( - onClick = { context.startActivity(Intent(context, AnchorsActivity::class.java)) } + onClick = { + context.startActivity(Intent(context, AnchorsPlanesHitTestActivity::class.java)) + } ) { - Text("Anchors") - } - TextButton( - onClick = { context.startActivity(Intent(context, PlanesActivity::class.java)) } - ) { - Text("Planes") - } - TextButton( - onClick = { context.startActivity(Intent(context, HitTestActivity::class.java)) } - ) { - Text("Hit Test") + Text("Anchors, Planes, HitTest") } TextButton( onClick = { context.startActivity(Intent(context, GeospatialActivity::class.java)) } diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchors/AnchorsActivity.kt b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchorsplaneshittest/AnchorsPlanesHitTestActivity.kt similarity index 63% rename from xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchors/AnchorsActivity.kt rename to xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchorsplaneshittest/AnchorsPlanesHitTestActivity.kt index 5df189a6b4580..a7abeb9c419e2 100644 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchors/AnchorsActivity.kt +++ b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/anchorsplaneshittest/AnchorsPlanesHitTestActivity.kt @@ -14,13 +14,15 @@ * limitations under the License. */ -package androidx.xr.arcore.apps.whitebox.mobile.anchors +package androidx.xr.arcore.apps.whitebox.mobile.anchorsplaneshittest import android.opengl.EGL14 import android.opengl.GLES11Ext import android.opengl.GLES30 import android.opengl.GLSurfaceView +import android.opengl.Matrix import android.os.Bundle +import android.view.MotionEvent import android.view.Surface import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -44,12 +46,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope import androidx.opengl.EGLExt import androidx.opengl.EGLImageKHR +import androidx.window.layout.WindowMetricsCalculator import androidx.xr.arcore.Anchor import androidx.xr.arcore.AnchorCreateSuccess +import androidx.xr.arcore.HitResult +import androidx.xr.arcore.Plane import androidx.xr.arcore.apps.whitebox.mobile.common.ArCoreVerificationHelper import androidx.xr.arcore.apps.whitebox.mobile.common.BackToMainActivityButton import androidx.xr.arcore.apps.whitebox.mobile.common.SessionLifecycleHelper @@ -60,28 +67,53 @@ import androidx.xr.arcore.apps.whitebox.mobile.samplerender.Shader import androidx.xr.arcore.apps.whitebox.mobile.samplerender.Texture import androidx.xr.arcore.apps.whitebox.mobile.samplerender.maybeThrowGLException import androidx.xr.arcore.apps.whitebox.mobile.samplerender.renderers.BackgroundRenderer +import androidx.xr.arcore.apps.whitebox.mobile.samplerender.renderers.PlaneRenderer +import androidx.xr.arcore.hitTest import androidx.xr.arcore.playservices.ArCoreRuntime +import androidx.xr.arcore.playservices.UnsupportedArCoreCompatApi import androidx.xr.arcore.playservices.cameraState +import androidx.xr.runtime.Config +import androidx.xr.runtime.Config.PlaneTrackingMode import androidx.xr.runtime.Log import androidx.xr.runtime.Session import androidx.xr.runtime.TrackingState import androidx.xr.runtime.math.Matrix4 import androidx.xr.runtime.math.Pose +import androidx.xr.runtime.math.Ray +import androidx.xr.runtime.math.Vector3 import java.io.IOException +import java.util.concurrent.ArrayBlockingQueue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch /** Activity to test the Anchor APIs. */ -class AnchorsActivity : +class AnchorsPlanesHitTestActivity : ComponentActivity(), DefaultLifecycleObserver, SampleRender.Companion.Renderer { + companion object { + private const val TAG = "AnchorsPlanesHitTestActivity" + private const val MAX_HIT_TEST_RESULTS = 10 + } + private lateinit var session: Session private lateinit var sessionHelper: SessionLifecycleHelper private lateinit var surfaceView: GLSurfaceView private lateinit var backgroundRenderer: BackgroundRenderer private lateinit var renderer: SampleRender + private lateinit var planeRenderer: PlaneRenderer private lateinit var virtualSceneFramebuffer: Framebuffer + private lateinit var updatePlanesJob: Job + private var screenWidth: Int = 1 + private var screenHeight: Int = 1 private var image: EGLImageKHR? = null + private val foundPlanes = mutableStateListOf() + private var foundHits = MutableStateFlow>(mutableStateListOf()) private val anchors = mutableStateListOf() private val arCoreVerificationHelper: ArCoreVerificationHelper = ArCoreVerificationHelper(this, onArCoreVerified = { sessionHelper.tryCreateSession() }) + private val queuedTaps: ArrayBlockingQueue = ArrayBlockingQueue(16) // Virtual object (ARCore pawn) private lateinit var virtualObjectMesh: Mesh @@ -102,6 +134,7 @@ class AnchorsActivity : sessionHelper = SessionLifecycleHelper( this, + Config(planeTracking = PlaneTrackingMode.HORIZONTAL_AND_VERTICAL), onSessionAvailable = { session -> this.session = session surfaceView = GLSurfaceView(this) @@ -113,23 +146,51 @@ class AnchorsActivity : }, ) sessionHelper.tryCreateSession() + + val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this) + val currentBounds = windowMetrics.bounds + + screenWidth = currentBounds.width() + screenHeight = currentBounds.height() } - override fun onResume(owner: LifecycleOwner) { + override fun onResume() { + super.onResume() + if (::session.isInitialized) { + val supervisorJob = SupervisorJob() + val scope = CoroutineScope(supervisorJob + lifecycleScope.coroutineContext) + updatePlanesJob = scope.launch { Plane.subscribe(session).collect { updatePlanes(it) } } + } if (::surfaceView.isInitialized) { surfaceView.onResume() + surfaceView.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_UP) { + queuedTaps.add(event) + } + true + } } } override fun onPause(owner: LifecycleOwner) { + super.onPause() if (::surfaceView.isInitialized) { surfaceView.onPause() } + if (::updatePlanesJob.isInitialized) { + updatePlanesJob.cancel() + } + } + + private fun updatePlanes(planes: Collection) { + foundPlanes.clear() + foundPlanes.addAll(planes) } override fun onSurfaceCreated(render: SampleRender) { try { backgroundRenderer = BackgroundRenderer(render) + planeRenderer = PlaneRenderer(render) virtualSceneFramebuffer = Framebuffer(render, width = 1, height = 1) // Virtual object to render (ARCore pawn) @@ -214,6 +275,15 @@ class AnchorsActivity : projectionMatrix = cameraState.projectionMatrix!! viewMatrix = cameraState.viewMatrix!! + planeRenderer.drawPlanes( + render, + Plane.subscribe(session).value, + checkNotNull(cameraState.displayOrientedPose) { + "cameraState.displayOrientedPose is null" + }, + projectionMatrix.copy().data, + ) + // Visualize anchors render.clear(virtualSceneFramebuffer, 0f, 0f, 0f, 0f) for (anchor in anchors) { @@ -230,6 +300,17 @@ class AnchorsActivity : render.draw(virtualObjectMesh, virtualObjectShader, virtualSceneFramebuffer) } + val drainedTaps = ArrayList() + queuedTaps.drainTo(drainedTaps) + for (tap in drainedTaps) { + getHits(tap.x, tap.y) + } + + for (hit in foundHits.value) { + addAnchor(hit.hitPose) + } + foundHits.value = emptyList() // So we don't keep duplicating anchors + // Draw the virtual scene. backgroundRenderer.drawVirtualScene( render, @@ -254,13 +335,11 @@ class AnchorsActivity : verticalAlignment = Alignment.CenterVertically, ) { BackToMainActivityButton() - Button( - onClick = { addAnchor(cameraState?.cameraPose!!) }, - enabled = hasCameraTracking, - ) { + Button(onClick = { deleteAllAnchors() }, enabled = hasCameraTracking) { Text( - text = if (hasCameraTracking) "Add anchor" else "Camera not tracking", - fontSize = 30.sp, + text = + if (hasCameraTracking) "Clear anchors" else "Camera not tracking", + fontSize = 15.sp, ) } } @@ -268,16 +347,36 @@ class AnchorsActivity : ) { innerPadding -> Column( modifier = - Modifier.background(color = Color.White) - .fillMaxHeight() - .fillMaxWidth() - .padding(innerPadding) + Modifier.background(color = Color.White).fillMaxWidth().padding(innerPadding) ) { + planesAndHitsInfo() AndroidView(modifier = Modifier.fillMaxSize(), factory = { _ -> surfaceView }) } } } + @Composable + private fun planesAndHitsInfo() { + Column( + modifier = Modifier.background(color = Color.White).fillMaxWidth().fillMaxHeight(.15f) + ) { + for (hitResult in foundHits.value) { + Text( + text = + "Hit Result: ${hitResult.trackable.hashCode()} - ${hitResult.hitPose.translation}", + fontSize = 14.sp, + ) + } + for (plane in foundPlanes) { + Text( + text = + "Tracking Plane: ${plane.hashCode()} - ${plane.state.value.trackingState} - ${plane.type} - ${plane.state.value.label}", + fontSize = 14.sp, + ) + } + } + } + private fun addAnchor(anchorPose: Pose) { val anchorResult = try { @@ -293,12 +392,60 @@ class AnchorsActivity : anchors.add(anchorResult.anchor) } - private fun deleteAnchor(anchor: Anchor) { - anchor.detach() - anchors.remove(anchor) + private fun deleteAllAnchors() { + for (anchor in anchors) { + anchor.detach() + } + anchors.clear() } - companion object { - const val ACTIVITY_NAME = "AnchorsActivity" + @OptIn(UnsupportedArCoreCompatApi::class) + private fun getHits(x: Float, y: Float) { + if (lifecycle.currentStateFlow.value != Lifecycle.State.RESUMED) { + return + } + val cameraState = session.state.value.cameraState + if (cameraState == null || cameraState.displayOrientedPose == null) { + return + } + val pose = cameraState.displayOrientedPose!! + var projMatrix = FloatArray(16) + var viewMatrix = FloatArray(16) + projMatrix = cameraState.projectionMatrix!!.data + viewMatrix = cameraState.viewMatrix!!.data + + val vpMatrix = FloatArray(16) + val invVpMatrix = FloatArray(16) + Matrix.multiplyMM(vpMatrix, 0, projMatrix, 0, viewMatrix, 0) + Matrix.invertM(invVpMatrix, 0, vpMatrix, 0) + + val xNDC = (x / screenWidth) * 2 - 1 + val yNDC = 1 - (y / screenHeight) * 2 + val touchNDC = floatArrayOf(xNDC, yNDC, -1f, 1f) + val worldPointNear = FloatArray(4) + Matrix.multiplyMV(worldPointNear, 0, invVpMatrix, 0, touchNDC, 0) + + val nearX = worldPointNear[0] / worldPointNear[3] + val nearY = worldPointNear[1] / worldPointNear[3] + val nearZ = worldPointNear[2] / worldPointNear[3] + + val cameraPose = cameraState.displayOrientedPose + val direction = + Vector3( + nearX - cameraPose!!.translation.x, + nearY - cameraPose!!.translation.y, + nearZ - cameraPose!!.translation.z, + ) + + val ray = Ray(cameraPose.translation, direction) + val hitResults = hitTest(session, ray) + if (hitResults.isNotEmpty()) { + val newHits = mutableStateListOf() + newHits.addAll(hitResults) + while (newHits.size > MAX_HIT_TEST_RESULTS) { + newHits.removeAt(0) + } + foundHits.value = newHits + } } } diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/hittest/HitTestActivity.kt b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/hittest/HitTestActivity.kt deleted file mode 100644 index dcb7ca2bc89c4..0000000000000 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/hittest/HitTestActivity.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2025 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.xr.arcore.apps.whitebox.mobile.hittest - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.xr.arcore.HitResult -import androidx.xr.arcore.Plane -import androidx.xr.arcore.apps.whitebox.mobile.common.ArCoreVerificationHelper -import androidx.xr.arcore.apps.whitebox.mobile.common.BackToMainActivityButton -import androidx.xr.arcore.apps.whitebox.mobile.common.SessionLifecycleHelper -import androidx.xr.arcore.hitTest -import androidx.xr.arcore.playservices.UnsupportedArCoreCompatApi -import androidx.xr.arcore.playservices.cameraState -import androidx.xr.runtime.Config -import androidx.xr.runtime.Config.PlaneTrackingMode -import androidx.xr.runtime.Session -import androidx.xr.runtime.math.Ray -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch - -/** Activity to test the hit test APIs. */ -class HitTestActivity : ComponentActivity() { - companion object { - private const val TAG = "HitTestActivity" - private const val MAX_HIT_TEST_RESULTS = 10 - } - - private lateinit var session: Session - private lateinit var sessionHelper: SessionLifecycleHelper - private lateinit var updatePlanesJob: Job - private val foundPlanes = mutableStateListOf() - private var foundHits = MutableStateFlow>(mutableStateListOf()) - private val arCoreVerificationHelper: ArCoreVerificationHelper = - ArCoreVerificationHelper(this, onArCoreVerified = { sessionHelper.tryCreateSession() }) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - lifecycle.addObserver(arCoreVerificationHelper) - sessionHelper = - SessionLifecycleHelper( - this, - Config(planeTracking = PlaneTrackingMode.HORIZONTAL_AND_VERTICAL), - onSessionAvailable = { session -> - this.session = session - setContent { MainPanel() } - }, - onSessionCreateActionRequired = { result -> - arCoreVerificationHelper.handleSessionCreateActionRequired(result) - }, - ) - sessionHelper.tryCreateSession() - } - - @OptIn(UnsupportedArCoreCompatApi::class) - override fun onResume() { - super.onResume() - if (::session.isInitialized) { - val supervisorJob = SupervisorJob() - val scope = CoroutineScope(supervisorJob + lifecycleScope.coroutineContext) - updatePlanesJob = scope.launch { Plane.subscribe(session).collect { updatePlanes(it) } } - } - } - - override fun onPause() { - super.onPause() - if (::updatePlanesJob.isInitialized) { - updatePlanesJob.cancel() - } - } - - private fun updatePlanes(planes: Collection) { - foundPlanes.clear() - foundPlanes.addAll(planes) - } - - @OptIn(UnsupportedArCoreCompatApi::class) - private fun getHits() { - if (lifecycle.currentStateFlow.value == Lifecycle.State.RESUMED) { - val cameraState = session.state.value.cameraState - if (cameraState == null || cameraState.displayOrientedPose == null) { - return - } - val pose = cameraState.displayOrientedPose!! - val ray = Ray(pose.translation, pose.forward) - val hitResults = hitTest(session, ray) - if (hitResults.isNotEmpty()) { - val newHits = mutableStateListOf() - newHits.addAll(hitResults) - while (newHits.size > MAX_HIT_TEST_RESULTS) { - newHits.removeAt(0) - } - foundHits.value = newHits - } - } - } - - @SuppressLint("StateFlowValueCalledInComposition") - @Composable - private fun MainPanel() { - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 50.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - BackToMainActivityButton() - Text( - textAlign = TextAlign.Center, - text = "Hit Test", - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - ) - } - }, - ) { innerPadding -> - Column( - modifier = - Modifier.background(color = Color.White) - .fillMaxHeight() - .fillMaxWidth() - .padding(innerPadding) - .verticalScroll(rememberScrollState()) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Button(onClick = { getHits() }) { Text(text = "Hit Test", fontSize = 30.sp) } - } - for (plane in foundPlanes) { - Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 15.dp, horizontal = 10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = - "Tracking Plane: ${plane.hashCode()} - ${plane.state.value.trackingState} - ${plane.type} - ${plane.state.value.label}", - fontSize = 14.sp, - modifier = Modifier.weight(1f), - ) - } - } - for (hitResult in foundHits.value) { - Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 15.dp, horizontal = 10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = - "Hit Result: ${hitResult.trackable.hashCode()} - ${hitResult.hitPose.translation}", - fontSize = 14.sp, - modifier = Modifier.weight(1f), - ) - } - } - } - } - } -} diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/planes/PlanesActivity.kt b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/planes/PlanesActivity.kt deleted file mode 100644 index e63826427c0ac..0000000000000 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/planes/PlanesActivity.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2025 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.xr.arcore.apps.whitebox.mobile.planes - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.lifecycleScope -import androidx.xr.arcore.Plane -import androidx.xr.arcore.apps.whitebox.mobile.common.ArCoreVerificationHelper -import androidx.xr.arcore.apps.whitebox.mobile.common.BackToMainActivityButton -import androidx.xr.arcore.apps.whitebox.mobile.common.SessionLifecycleHelper -import androidx.xr.runtime.Config -import androidx.xr.runtime.Config.PlaneTrackingMode -import androidx.xr.runtime.Session -import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch - -/** Activity to test the Planes APIs. */ -class PlanesActivity : ComponentActivity() { - private lateinit var session: Session - private lateinit var sessionHelper: SessionLifecycleHelper - private lateinit var updateJob: CompletableJob - - private val foundPlanes = mutableStateListOf() - private val arCoreVerificationHelper: ArCoreVerificationHelper = - ArCoreVerificationHelper(this, onArCoreVerified = { sessionHelper.tryCreateSession() }) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - lifecycle.addObserver(arCoreVerificationHelper) - sessionHelper = - SessionLifecycleHelper( - this, - Config(planeTracking = PlaneTrackingMode.HORIZONTAL_AND_VERTICAL), - onSessionAvailable = { session -> - this.session = session - setContent { MainPanel() } - }, - onSessionCreateActionRequired = { result -> - arCoreVerificationHelper.handleSessionCreateActionRequired(result) - }, - ) - sessionHelper.tryCreateSession() - } - - override fun onResume() { - super.onResume() - if (::session.isInitialized) { - updateJob = - SupervisorJob( - lifecycleScope.launch { Plane.subscribe(session).collect { updatePlanes(it) } } - ) - } - } - - override fun onPause() { - super.onPause() - if (::updateJob.isInitialized) { - updateJob.complete() - } - } - - private fun updatePlanes(planes: Collection) { - foundPlanes.clear() - foundPlanes.addAll(planes) - } - - @SuppressLint("StateFlowValueCalledInComposition") - @Composable - private fun MainPanel() { - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 50.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - BackToMainActivityButton() - Text( - textAlign = TextAlign.Center, - text = "Planes", - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - ) - } - }, - ) { innerPadding -> - Column( - modifier = - Modifier.background(color = Color.White) - .fillMaxHeight() - .fillMaxWidth() - .padding(innerPadding) - .verticalScroll(rememberScrollState()) - ) { - for (plane in foundPlanes) { - val planeState = plane.state.value - Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 15.dp, horizontal = 10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = - "${plane.hashCode()} - ${planeState.trackingState} - ${plane.type} - ${planeState.label}", - fontSize = 14.sp, - modifier = Modifier.weight(1f), - ) - } - } - } - } - } - - companion object { - const val ACTIVITY_NAME = "PlanesActivity" - } -} diff --git a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/samplerender/renderers/PlaneRenderer.kt b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/samplerender/renderers/PlaneRenderer.kt index 39b76903f19c2..0c1f5b342475e 100644 --- a/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/samplerender/renderers/PlaneRenderer.kt +++ b/xr/arcore/integration-tests/whitebox-mobile/src/main/kotlin/androidx/xr/arcore/apps/whitebox/mobile/samplerender/renderers/PlaneRenderer.kt @@ -274,6 +274,22 @@ class PlaneRenderer(render: SampleRender) { }, ) + val planeCategory = plane.state.value.label.toString() + val planeTint = + if (PLANE_COLORS.containsKey(planeCategory)) { + PLANE_COLORS[planeCategory] + } else if (planeCategory == "UNKNOWN") { + if (plane.type.toString() == "VERTICAL") { + PLANE_COLORS["WALL"] + } else { + PLANE_COLORS["FLOOR"] + } + } else { + DEFAULT_PLANE_COLOR + } + + shader.setVec4("u_Tint", planeTint!!) + // Set the position of the plane vertexBufferObject.set(vertexBuffer) indexBufferObject.set(indexBuffer) @@ -322,6 +338,15 @@ class PlaneRenderer(render: SampleRender) { // occlusionShrink: occluded planes will fade out between alpha = 0 and 1/occlusionShrink private val GRID_CONTROL = floatArrayOf(0.2f, 0.4f, 2.0f, 1.5f) + private val DEFAULT_PLANE_COLOR = floatArrayOf(1f, 1f, 1f, 1f) + private val PLANE_COLORS = + mapOf( + "WALL" to floatArrayOf(0f, 1f, 0f, 1f), + "FLOOR" to floatArrayOf(0f, 0f, 1f, 1f), + "CEILING" to floatArrayOf(0f, 1f, 1f, 1f), + "TABLE" to floatArrayOf(1f, 0f, 1f, 1f), + ) + // Calculate the normal distance to plane from cameraPose, the given planePose should have y // axis parallel to plane's normal, for example plane's center pose or hit test pose. fun calculateDistanceToPlane(planePose: Pose, cameraPose: Pose): Float { diff --git a/xr/glimmer/benchmark/build.gradle b/xr/glimmer/benchmark/build.gradle index 98bddb898f9c3..4109c9064bd9e 100644 --- a/xr/glimmer/benchmark/build.gradle +++ b/xr/glimmer/benchmark/build.gradle @@ -25,6 +25,7 @@ plugins { dependencies { androidTestImplementation(project(":xr:glimmer:glimmer")) + androidTestImplementation(project(":xr:glimmer:test-utils")) androidTestImplementation(project(":benchmark:benchmark-junit4")) androidTestImplementation(project(":compose:benchmark-utils")) androidTestImplementation(libs.testRules) diff --git a/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/stack/VerticalStackBenchmark.kt b/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/stack/VerticalStackBenchmark.kt index 590bd842796b5..5a579e8751596 100644 --- a/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/stack/VerticalStackBenchmark.kt +++ b/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/stack/VerticalStackBenchmark.kt @@ -18,20 +18,29 @@ package androidx.xr.glimmer.benchmark.stack import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.testutils.LayeredComposeTestCase +import androidx.compose.testutils.assertNoPendingChanges import androidx.compose.testutils.benchmark.ComposeBenchmarkRule import androidx.compose.testutils.benchmark.benchmarkFirstCompose import androidx.compose.testutils.benchmark.benchmarkToFirstPixel +import androidx.compose.testutils.doFramesUntilNoChangesPending import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextMotion +import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.xr.glimmer.GlimmerTheme import androidx.xr.glimmer.LocalTextStyle import androidx.xr.glimmer.Text +import androidx.xr.glimmer.stack.StackState import androidx.xr.glimmer.stack.VerticalStack +import androidx.xr.glimmer.testutils.createGlimmerRule +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -40,23 +49,52 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class VerticalStackBenchmark { - @get:Rule val benchmarkRule = ComposeBenchmarkRule() + @get:Rule(0) val benchmarkRule = ComposeBenchmarkRule() + + @get:Rule(1) val glimmerRule = createGlimmerRule() @Test fun first_compose() { - benchmarkRule.benchmarkFirstCompose { StackTestCase } + benchmarkRule.benchmarkFirstCompose { VerticalStackTestCase() } } @Test fun first_pixel() { - benchmarkRule.benchmarkToFirstPixel { StackTestCase } + benchmarkRule.benchmarkToFirstPixel { VerticalStackTestCase() } + } + + @Test + fun scroll_firstFrame() { + with(benchmarkRule) { + runBenchmarkFor({ VerticalStackTestCase() }) { + runOnUiThread { doFramesUntilNoChangesPending() } + + measureRepeatedOnUiThread { + runWithMeasurementDisabled { + getTestCase().resetScroll() + doFramesUntilNoChangesPending() + getTestCase().beforeScroll() + assertNoPendingChanges() + } + + getTestCase().scroll() + doFrame() + + runWithMeasurementDisabled { getTestCase().afterScroll() } + } + } + } } } -private object StackTestCase : LayeredComposeTestCase() { +private class VerticalStackTestCase : LayeredComposeTestCase() { + + private val stackState = StackState() + private var stackHeightPx = 0f + @Composable override fun MeasuredContent() { - VerticalStack { + VerticalStack(state = stackState, modifier = Modifier.height(StackHeight)) { items(10) { index -> Box(modifier = Modifier.focusable().itemDecoration(RectangleShape)) { Text( @@ -70,6 +108,25 @@ private object StackTestCase : LayeredComposeTestCase() { @Composable override fun ContentWrappers(content: @Composable () -> Unit) { + with(LocalDensity.current) { stackHeightPx = StackHeight.toPx() } GlimmerTheme { content() } } + + fun resetScroll() { + runBlocking { stackState.scrollToItem(0) } + } + + fun beforeScroll() { + assertEquals(0, stackState.topItem) + } + + fun scroll() { + stackState.dispatchRawDelta(stackHeightPx) + } + + fun afterScroll() { + assertEquals(1, stackState.topItem) + } } + +private val StackHeight = 300.dp diff --git a/xr/glimmer/glimmer/build.gradle b/xr/glimmer/glimmer/build.gradle index 394b0f54d1f5c..68350a93663d5 100644 --- a/xr/glimmer/glimmer/build.gradle +++ b/xr/glimmer/glimmer/build.gradle @@ -40,6 +40,7 @@ dependencies { androidTestImplementation(project(":compose:test-utils")) androidTestImplementation(project(":test:screenshot:screenshot")) androidTestImplementation(project(":xr:glimmer:glimmer:glimmer-samples")) + androidTestImplementation(project(":xr:glimmer:test-utils")) androidTestImplementation("androidx.core:core:1.16.0") androidTestImplementation(libs.espressoCore) diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ButtonTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ButtonTest.kt index 2cd3853eb6476..8cce532a28c54 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ButtonTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ButtonTest.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.junit4.v2.createComposeRule @@ -56,6 +55,7 @@ import androidx.compose.ui.unit.width import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Rule diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/CardTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/CardTest.kt index 97ea5c4828ab6..6a978fd63ac75 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/CardTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/CardTest.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsEnabled -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.isFocusable @@ -78,6 +77,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress import androidx.xr.glimmer.samples.placeholderImagePainter +import androidx.xr.glimmer.testutils.captureToImage +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/DepthTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/DepthTest.kt index be1b1cd4e967e..9cb78ec55c4ca 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/DepthTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/DepthTest.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.platform.InspectableValue import androidx.compose.ui.platform.ValueElement import androidx.compose.ui.platform.isDebugInspectorInfoEnabled import androidx.compose.ui.platform.testTag -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.Density @@ -45,6 +44,7 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.After diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IconTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IconTest.kt index 47f46ee80aec7..6fbba8ab91f76 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IconTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IconTest.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertContentDescriptionEquals import androidx.compose.ui.test.assertHeightIsEqualTo import androidx.compose.ui.test.assertWidthIsEqualTo -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.Density @@ -55,6 +54,7 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage import com.google.common.truth.Truth.assertThat import kotlin.math.roundToInt import kotlinx.coroutines.test.StandardTestDispatcher diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IndirectPointerGestureTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IndirectPointerGestureTest.kt index e5082c998450e..0b9b25e69a788 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IndirectPointerGestureTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/IndirectPointerGestureTest.kt @@ -49,6 +49,7 @@ import androidx.test.core.view.MotionEventBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Rule diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ListItemTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ListItemTest.kt index a18d4b0b4474a..36c686b237f7d 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ListItemTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ListItemTest.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsEnabled -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.isFocusable @@ -67,6 +66,8 @@ import androidx.core.view.InputDeviceCompat.SOURCE_TOUCH_NAVIGATION import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ScreenshotUtils.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ScreenshotUtils.kt index bff378185ef3e..840eafaffe192 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ScreenshotUtils.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/ScreenshotUtils.kt @@ -31,12 +31,12 @@ import androidx.compose.runtime.Composable import androidx.compose.testutils.assertAgainstGolden import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.onRoot import androidx.compose.ui.unit.dp import androidx.test.screenshot.AndroidXScreenshotTestRule import androidx.test.screenshot.matchers.MSSIMMatcher +import androidx.xr.glimmer.testutils.captureToImage import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/SurfaceTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/SurfaceTest.kt index ba795e7e499d2..d0149a38158a6 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/SurfaceTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/SurfaceTest.kt @@ -19,7 +19,6 @@ package androidx.xr.glimmer import android.os.Build import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background -import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.FocusInteraction import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource @@ -74,7 +73,6 @@ import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.assertTextEquals -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.isFocusable import androidx.compose.ui.test.isFocused import androidx.compose.ui.test.isNotFocusable @@ -91,6 +89,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress import androidx.test.screenshot.matchers.MSSIMMatcher +import androidx.xr.glimmer.testutils.captureToImage +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TextTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TextTest.kt index 1ef5d23cd40ab..8994edf2405f9 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TextTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TextTest.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.semantics.SemanticsActions import androidx.compose.ui.semantics.getOrNull import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assert -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag @@ -55,6 +54,7 @@ import androidx.compose.ui.unit.width import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Rule diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TitleChipTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TitleChipTest.kt index f06b1d56bf9c0..bca15e5b03734 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TitleChipTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/TitleChipTest.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assert -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.isNotFocusable @@ -45,6 +44,7 @@ import androidx.compose.ui.unit.width import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress +import androidx.xr.glimmer.testutils.captureToImage import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Rule diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/list/BaseListTestWithOrientation.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/list/BaseListTestWithOrientation.kt index 293dc3fa6d39c..ee195928ad3b0 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/list/BaseListTestWithOrientation.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/list/BaseListTestWithOrientation.kt @@ -58,9 +58,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.xr.glimmer.GlimmerRule import androidx.xr.glimmer.Text -import androidx.xr.glimmer.createGlimmerRule +import androidx.xr.glimmer.testutils.createGlimmerRule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.StandardTestDispatcher @@ -71,7 +70,7 @@ abstract class BaseListTestWithOrientation(protected val orientation: Orientatio val testDispatcher = StandardTestDispatcher() @get:Rule(0) val rule: ComposeContentTestRule = createComposeRule(testDispatcher) - @get:Rule(1) val glimmerRule: GlimmerRule = createGlimmerRule() + @get:Rule(1) val glimmerRule = createGlimmerRule() val vertical: Boolean get() = orientation == Orientation.Vertical diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/StackStateTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/StackStateTest.kt index e47ebbc06d4e2..77941b118623e 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/StackStateTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/StackStateTest.kt @@ -43,8 +43,8 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.xr.glimmer.Text -import androidx.xr.glimmer.createGlimmerRule import androidx.xr.glimmer.performIndirectSwipe +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackFocusTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackFocusTest.kt index 5f2bafd532616..43da768247228 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackFocusTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackFocusTest.kt @@ -44,8 +44,8 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import androidx.xr.glimmer.Text -import androidx.xr.glimmer.createGlimmerRule import androidx.xr.glimmer.performIndirectSwipe +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.StandardTestDispatcher diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackMaskingTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackMaskingTest.kt index 8c95a16fa3591..82edce3bbb39b 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackMaskingTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackMaskingTest.kt @@ -42,7 +42,6 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.toPixelMap import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.testTag -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag @@ -52,8 +51,9 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import androidx.xr.glimmer.Text -import androidx.xr.glimmer.createGlimmerRule import androidx.xr.glimmer.performIndirectSwipe +import androidx.xr.glimmer.testutils.captureToImage +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlin.math.sqrt diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackScreenshotTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackScreenshotTest.kt index 86251a0de8d9b..bb5ac7bd98c0f 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackScreenshotTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackScreenshotTest.kt @@ -26,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.testutils.assertAgainstGolden import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput @@ -43,6 +42,7 @@ import androidx.xr.glimmer.GOLDEN_DIRECTORY import androidx.xr.glimmer.Text import androidx.xr.glimmer.samples.VerticalStackSample import androidx.xr.glimmer.setGlimmerThemeContent +import androidx.xr.glimmer.testutils.captureToImage import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Rule import org.junit.Test diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackTest.kt b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackTest.kt index 976a2727299e2..e9b70a05ae423 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackTest.kt +++ b/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/stack/VerticalStackTest.kt @@ -43,7 +43,6 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed -import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.getBoundsInRoot import androidx.compose.ui.test.junit4.StateRestorationTester import androidx.compose.ui.test.junit4.v2.createComposeRule @@ -59,11 +58,12 @@ import androidx.compose.ui.unit.size import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import androidx.xr.glimmer.Text -import androidx.xr.glimmer.createGlimmerRule import androidx.xr.glimmer.performIndirectMove import androidx.xr.glimmer.performIndirectPress import androidx.xr.glimmer.performIndirectRelease import androidx.xr.glimmer.performIndirectSwipe +import androidx.xr.glimmer.testutils.captureToImage +import androidx.xr.glimmer.testutils.createGlimmerRule import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.CoroutineScope diff --git a/xr/glimmer/test-utils/build.gradle b/xr/glimmer/test-utils/build.gradle new file mode 100644 index 0000000000000..1ee6dd13e2e87 --- /dev/null +++ b/xr/glimmer/test-utils/build.gradle @@ -0,0 +1,46 @@ +/* + * Copyright 2026 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. + */ + +import androidx.build.SoftwareType + +plugins { + id("AndroidXPlugin") + id("AndroidXComposePlugin") + id("com.android.library") +} + +dependencies { + api("androidx.compose.ui:ui:1.10.0") + api("androidx.compose.ui:ui-test:1.10.0") + api(libs.junit) + implementation(libs.testCore) + implementation(libs.testRules) +} + +android { + compileSdk { version = release(35) } + namespace = "androidx.xr.glimmer.testutils" + defaultConfig { + minSdk { version = release(33) } + } +} + +androidx { + name = "Jetpack Compose Glimmer Internal Test Utils" + type = SoftwareType.INTERNAL_TEST_LIBRARY + inceptionYear = "2026" + description = "Jetpack Compose Glimmer internal test utils." +} diff --git a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/GlimmerRule.kt b/xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerRule.kt similarity index 87% rename from xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/GlimmerRule.kt rename to xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerRule.kt index fd4ce2c4d0fcc..7e59e66390639 100644 --- a/xr/glimmer/glimmer/src/androidTest/kotlin/androidx/xr/glimmer/GlimmerRule.kt +++ b/xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerRule.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package androidx.xr.glimmer +package androidx.xr.glimmer.testutils import android.app.Instrumentation -import android.os.Build.VERSION.SDK_INT import androidx.compose.ui.ComposeUiFlags import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.input.key.Key @@ -25,7 +24,7 @@ import androidx.compose.ui.input.key.nativeKeyCode import androidx.test.platform.app.InstrumentationRegistry import org.junit.rules.ExternalResource -internal fun createGlimmerRule(): GlimmerRule = GlimmerRule() +fun createGlimmerRule(): GlimmerRule = GlimmerRule() /** * Enters non-touch mode for tests so that Glimmer's focusables can be focused. This also enables @@ -33,12 +32,13 @@ internal fun createGlimmerRule(): GlimmerRule = GlimmerRule() * focusable item. */ @OptIn(ExperimentalComposeUiApi::class) -class GlimmerRule() : ExternalResource() { +class GlimmerRule : ExternalResource() { // Save the original flag value right before the test runs. private val savedInitialFocusAvailability = ComposeUiFlags.isInitialFocusOnFocusableAvailable override fun before() { + assumeGlimmerMinSdk() ComposeUiFlags.isInitialFocusOnFocusableAvailable = true InstrumentationRegistry.getInstrumentation().setInTouchModeCompat(false) } @@ -47,7 +47,7 @@ class GlimmerRule() : ExternalResource() { // Restore the flag to its original value after the test finishes. ComposeUiFlags.isInitialFocusOnFocusableAvailable = savedInitialFocusAvailability // TODO(b/267253920): Add a compose test API to set/reset InputMode. - InstrumentationRegistry.getInstrumentation().resetInTouchModeCompat() + InstrumentationRegistry.getInstrumentation().resetInTouchMode() } private fun Instrumentation.setInTouchModeCompat(touchMode: Boolean) { @@ -59,8 +59,4 @@ class GlimmerRule() : ExternalResource() { sendKeyDownUpSync(Key.Grave.nativeKeyCode) } } - - private fun Instrumentation.resetInTouchModeCompat() { - if (SDK_INT < 33) setInTouchMode(true) else resetInTouchMode() - } } diff --git a/xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerTestUtils.kt.kt b/xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerTestUtils.kt.kt new file mode 100644 index 0000000000000..444e1e6b88731 --- /dev/null +++ b/xr/glimmer/test-utils/src/main/kotlin/androidx/xr/glimmer/testutils/GlimmerTestUtils.kt.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 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.xr.glimmer.testutils + +import android.annotation.SuppressLint +import android.os.Build +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.captureToImage as captureToImageOriginal +import org.junit.Assume + +/** + * A wrapper around [captureToImage] that skips the test if the device does not satisfy the Glimmer + * min SDK requirements. + */ +fun SemanticsNodeInteraction.captureToImage(): ImageBitmap { + assumeGlimmerMinSdk() + return this.captureToImageOriginal() +} + +/** + * If the device is running an SDK lower than 33, this method will skip the test. + * + * The expected min SDK for Glimmer is 35, however, we test on 33+ for wider device coverage (some + * APIs are not available below 33). + */ +@SuppressLint("NewApi", "ObsoleteSdkInt") // We manually handle the API check below +internal fun assumeGlimmerMinSdk() { + Assume.assumeTrue( + "Skipping test: Glimmer tests should only run on SDK 33 or higher.", + Build.VERSION.SDK_INT >= 33, + ) +} diff --git a/xr/runtime/runtime/src/main/kotlin/androidx/xr/runtime/Config.kt b/xr/runtime/runtime/src/main/kotlin/androidx/xr/runtime/Config.kt index 667b5b458c600..d39d3ee78589a 100644 --- a/xr/runtime/runtime/src/main/kotlin/androidx/xr/runtime/Config.kt +++ b/xr/runtime/runtime/src/main/kotlin/androidx/xr/runtime/Config.kt @@ -195,12 +195,7 @@ constructor( } } - /** - * Feature that allows tracking of and provides information about scene planes. - * - * Setting this feature to [PlaneTrackingMode.HORIZONTAL_AND_VERTICAL] requires that the - * `SCENE_UNDERSTANDING_COARSE` Android permission is granted. - */ + /** Feature that allows tracking of and provides information about scene planes. */ @SuppressWarnings("HiddenSuperclass") public class PlaneTrackingMode private constructor( @@ -212,6 +207,17 @@ constructor( /** * Horizontal and vertical planes will be tracked. Note that setting this mode will * consume additional runtime resources. + * + * Supported runtimes: + * - OpenXR + * - Play Services + * + * Required permissions: + * - [SCENE_UNDERSTANDING_COARSE][androidx.xr.runtime.manifest.SCENE_UNDERSTANDING_COARSE] + * (OpenXR runtimes only) + * - [ACCESS_COARSE_LOCATION][android.Manifest.permission.ACCESS_COARSE_LOCATION] (Play + * Services runtimes only) + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) */ @JvmField public val HORIZONTAL_AND_VERTICAL: PlaneTrackingMode = PlaneTrackingMode(1) } @@ -221,12 +227,7 @@ constructor( } } - /** - * Feature that allows tracking of the user's hands and hand joints. - * - * Setting this feature to [HandTrackingMode.BOTH] requires that the `HAND_TRACKING` Android - * permission is granted by the calling application. - */ + /** Feature that allows tracking of the user's hands and hand joints. */ @SuppressWarnings("HiddenSuperclass") public class HandTrackingMode private constructor( @@ -238,6 +239,12 @@ constructor( /** * Both the left and right hands will be tracked. Note that setting this mode will * consume additional runtime resources. + * + * Supported runtimes: + * - OpenXR + * + * Required permissions: + * - [HAND_TRACKING][androidx.xr.runtime.manifest.HAND_TRACKING] */ @JvmField public val BOTH: HandTrackingMode = HandTrackingMode(1) } @@ -247,11 +254,7 @@ constructor( } } - /** - * Feature that allows tracking of the AR device. - * - * This feature does not require any additional application permissions. - */ + /** Feature that allows tracking of the AR device. */ @SuppressWarnings("HiddenSuperclass") public class DeviceTrackingMode private constructor( @@ -268,6 +271,13 @@ constructor( * The device pose will be tracked and the last known pose from the system at the time * of runtime update will be provided. Note that there is generally a delay between the * actual device pose and the pose provided by the system by the time of the update. + * + * Supported runtimes: + * - OpenXR + * - Play Services + * + * Required permissions: + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) */ @JvmField public val LAST_KNOWN: DeviceTrackingMode = DeviceTrackingMode(1) } @@ -277,13 +287,7 @@ constructor( } } - /** - * Feature that allows more accurate information about scene depth and meshes. - * - * Setting this feature to any of [DepthEstimationMode.RAW_ONLY], - * [DepthEstimationMode.SMOOTH_ONLY] or [DepthEstimationMode.SMOOTH_AND_RAW] requires that the - * `SCENE_UNDERSTANDING_FINE` Android permission is granted by the calling application. - */ + /** Feature that allows more accurate information about scene depth and meshes. */ @SuppressWarnings("HiddenSuperclass") public class DepthEstimationMode private constructor( @@ -293,15 +297,46 @@ constructor( /** No information about scene depth will be provided. */ @JvmField public val DISABLED: DepthEstimationMode = DepthEstimationMode(0) - /** Depth estimation will be enabled with raw depth and confidence. */ + /** + * Depth estimation will be enabled with raw depth and confidence. + * + * Supported runtimes: + * - OpenXR + * - Play Services (on supported devices) + * + * Required permissions: + * - [SCENE_UNDERSTANDING_FINE][androidx.xr.runtime.manifest.SCENE_UNDERSTANDING_FINE] + * (OpenXR runtimes only) + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) + */ @JvmField public val RAW_ONLY: DepthEstimationMode = DepthEstimationMode(1) - /** Depth estimation will be enabled with smooth depth and confidence. */ + /** + * Depth estimation will be enabled with smooth depth and confidence. + * + * Supported runtimes: + * - OpenXR + * - Play Services (on supported devices) + * + * Required permissions: + * - [SCENE_UNDERSTANDING_FINE][androidx.xr.runtime.manifest.SCENE_UNDERSTANDING_FINE] + * (OpenXR runtimes only) + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) + */ @JvmField public val SMOOTH_ONLY: DepthEstimationMode = DepthEstimationMode(2) /** * Depth estimation will be enabled with both raw and smooth depth and confidence. Note * that setting this mode will consume additional runtime resources. + * + * Supported runtimes: + * - OpenXR + * - Play Services (on supported devices) + * + * Required permissions: + * - [SCENE_UNDERSTANDING_FINE][androidx.xr.runtime.manifest.SCENE_UNDERSTANDING_FINE] + * (OpenXR runtimes only) + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) */ @JvmField public val SMOOTH_AND_RAW: DepthEstimationMode = DepthEstimationMode(3) } @@ -318,11 +353,7 @@ constructor( } } - /** - * Feature that allows anchors to be persisted through sessions. - * - * This feature does not require any additional application permissions. - */ + /** Feature that allows anchors to be persisted through sessions. */ @SuppressWarnings("HiddenSuperclass") public class AnchorPersistenceMode private constructor( @@ -331,7 +362,14 @@ constructor( public companion object { /** Anchors cannot be persisted. */ @JvmField public val DISABLED: AnchorPersistenceMode = AnchorPersistenceMode(0) - /** Anchors may be persisted and will be saved in the application's local storage. */ + /** + * Anchors may be persisted and will be saved in the application's local storage. + * + * Supported runtimes: + * - OpenXR + * + * Required permissions: None + */ @JvmField public val LOCAL: AnchorPersistenceMode = AnchorPersistenceMode(1) } @@ -358,10 +396,26 @@ constructor( /** Faces will not be tracked. */ @JvmField public val DISABLED: FaceTrackingMode = FaceTrackingMode(0) - /** Blend shapes of the user's face will be tracked. */ + /** + * Blend shapes of the user's face will be tracked. + * + * Supported runtimes: + * - OpenXR + * + * Required permissions: + * - [FACE_TRACKING][androidx.xr.runtime.manifest.FACE_TRACKING] + */ @JvmField public val BLEND_SHAPES: FaceTrackingMode = FaceTrackingMode(1) - /** Face meshes will be tracked using the front-facing camera. */ + /** + * Face meshes will be tracked using the front-facing camera. + * + * Supported runtimes: + * - Play Services + * + * Required permissions: + * - [CAMERA][android.Manifest.permission.CAMERA] + */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @JvmField public val MESHES: FaceTrackingMode = FaceTrackingMode(2) @@ -381,7 +435,7 @@ constructor( * This can work even where GPS accuracy is low, such as dense urban environments. Under * typical conditions, VPS can be expected to provide positional accuracy typically better * than 5 meters and often around 1 meter, and a rotational accuracy of better than 5 degrees. - * Use [Geospatial.checkVpsAvailability] to determine if a given location has VPS coverage. + * Use `Geospatial.checkVpsAvailability` to determine if a given location has VPS coverage. * - In outdoor environments with few or no overhead obstructions, GPS may be sufficient to * generate high accuracy poses. GPS accuracy may be low in dense urban environments and * indoors. @@ -395,21 +449,21 @@ constructor( ) : ConfigMode { public companion object { /** - * The Geospatial API is disabled. When GeospatialMode is disabled, current [Anchor] - * objects created from [Geospatial] will stop updating, and have their [TrackingState] + * The Geospatial API is disabled. When GeospatialMode is disabled, current `Anchor` + * objects created from `Geospatial` will stop updating, and have their [TrackingState] * set to [TrackingState.STOPPED]. */ @JvmField public val DISABLED: GeospatialMode = GeospatialMode(0) /** - * The Geospatial API is enabled. [Geospatial] should enter the running state shortly + * The Geospatial API is enabled. `Geospatial` should enter the running state shortly * after this mode is set. * * Using this mode requires your app do the following, depending on the Runtime: * * On mobile and projected devices: * - Include the - * [ACCESS_INTERNET](https://developer.android.com/training/basics/network-ops/connecting) + * [INTERNET](https://developer.android.com/training/basics/network-ops/connecting) * permission to the app's AndroidManifest * - Request and be granted the * [ACCESS_FINE_LOCATION permission](https://developer.android.com/training/location/permissions); @@ -420,7 +474,7 @@ constructor( * [dependencies for Google Play services](https://developers.google.com/android/guides/setup#declare-dependencies) * for instructions on how to include this library in your app. If this library is not * linked, [Session.configure] returns - * [SessionResultGooglePlayServicesLocationLibraryNotLinked]. + * [SessionConfigureGooglePlayServicesLocationLibraryNotLinked]. * * Location is tracked only while the [Session] is resumed. * @@ -431,6 +485,15 @@ constructor( * Not all devices support GeospatialMode.VPS_AND_GPS, use [ConfigMode.isSupported] to * check if the current device and selected camera support enabling this mode. These * checks are done in the call to [Session.configure]. + * + * Supported runtimes: + * - Play Services (on supported devices) + * - Projected + * + * Required permissions: + * - [INTERNET][android.Manifest.permission.INTERNET] + * - [ACCESS_FINE_LOCATION][android.Manifest.permission.ACCESS_FINE_LOCATION] + * - [CAMERA][android.Manifest.permission.CAMERA] (Play Services runtimes only) */ @JvmField public val VPS_AND_GPS: GeospatialMode = GeospatialMode(1) } @@ -440,12 +503,7 @@ constructor( } } - /** - * Feature that allows tracking of the user's eyes. - * - * Setting this feature to any mode other than [EyeTrackingMode.DISABLED] requires that the - * `EYE_TRACKING` Android permission is granted by the calling application. - */ + /** Feature that allows tracking of the user's eyes. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @SuppressWarnings("HiddenSuperclass") public class EyeTrackingMode private constructor(public val mode: Int) : ConfigMode { @@ -454,9 +512,23 @@ constructor( @JvmField public val DISABLED: EyeTrackingMode = EyeTrackingMode(0) /** * Enables coarse eye tracking, providing general gaze direction without high precision. + * + * Supported runtimes: + * - OpenXR + * + * Required permissions: + * - [EYE_TRACKING_COARSE][androidx.xr.runtime.manifest.EYE_TRACKING_COARSE] */ @JvmField public val COARSE_TRACKING: EyeTrackingMode = EyeTrackingMode(1) - /** Enables fine eye tracking, providing more precise gaze direction. */ + /** + * Enables fine eye tracking, providing more precise gaze direction. + * + * Supported runtimes: + * - OpenXR + * + * Required permissions: + * - [EYE_TRACKING_FINE][androidx.xr.runtime.manifest.EYE_TRACKING_FINE] + */ @JvmField public val FINE_TRACKING: EyeTrackingMode = EyeTrackingMode(2) } } @@ -468,10 +540,26 @@ constructor( @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public val mode: Int ) : ConfigMode { public companion object { - /** Use the world-facing camera. This is the default behavior across all devices. */ + /** + * Use the world-facing camera. This is the default behavior across all devices. + * + * Supported runtimes: + * - Play Services + * + * Required permissions: + * - [CAMERA][android.Manifest.permission.CAMERA] + */ @JvmField public val WORLD: CameraFacingDirection = CameraFacingDirection(0) - /** Use the user-facing camera. */ + /** + * Use the user-facing camera. + * + * Supported runtimes: + * - Play Services + * + * Required permissions: + * - [CAMERA][android.Manifest.permission.CAMERA] + */ @JvmField public val USER: CameraFacingDirection = CameraFacingDirection(1) } }