Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b93376c
Change log tag of MR2ProviderServiceAdapter
AquilesCanta Jan 2, 2026
2d4e3c0
Add tests for FastSafeIterableMap
marcellogalhardo Dec 23, 2025
cb3a0a3
Add dependency on androidx.collection
marcellogalhardo Dec 22, 2025
01de3bd
Optimize FastSafeIterableMap for zero-allocation iteration
marcellogalhardo Jan 5, 2026
960420d
Use single FastSafeIterableMap implementation
marcellogalhardo Jan 5, 2026
e241ca4
[TimePicker] Added sound and haptic feedback to time input errors
Jan 7, 2026
df9bc3b
Add Java JVM to project-creator script
Jan 12, 2026
283b17e
Remove billboard API and add samples
Dec 20, 2025
2fe11ec
Enable configuration caching for all Gradle plugins that use ProjectS…
liutikas Nov 10, 2025
f12cec3
Add PictureInPictureImpl for typically PiP usages
hwwang-google Jan 9, 2026
11b4748
Optimize PredictiveBackHandler lookups
marcellogalhardo Jan 13, 2026
074eb13
Throw on added abstract properties as well as functions
fsladkey Jan 8, 2026
aca8997
Allow new abstract methods to be added to sealed classes as long as n…
fsladkey Jan 5, 2026
8e2310b
Fix typo
yschimke Jan 13, 2026
84f43d9
Merge "Add Java JVM to project-creator script" into androidx-main
Jan 13, 2026
51c2f35
Merge "Enable configuration caching for all Gradle plugins that use P…
Jan 13, 2026
784363f
Introduce RemoteStateScope and use for id generation
yschimke Jan 9, 2026
394eb1f
Fix disabled button in SceneCore test app for FoV test
mrichards Jan 13, 2026
d09eba2
Introduce internal `Monitor` class
derekxu16 Dec 16, 2025
e2fed22
Merge "Fix typo" into androidx-main
Jan 13, 2026
15f9a24
Merge "Fix disabled button in SceneCore test app for FoV test" into a…
Jan 13, 2026
b4baec1
Merge "Throw on added abstract properties as well as functions" into …
Jan 13, 2026
4e7e520
Prevent `Snapshot.sendApplyNotifications` calls from returning too early
derekxu16 Oct 30, 2025
fbdb4c1
Merge changes I662c67c5,I284858aa,I8652d78e,Ie6a96556 into androidx-main
marcellogalhardo Jan 13, 2026
5da09b7
Merge "Introduce RemoteStateScope and use for id generation" into and…
yschimke Jan 13, 2026
db5b720
Merge "Remove billboard API and add samples" into androidx-main
Jan 13, 2026
78a2832
Merge "Optimize PredictiveBackHandler lookups" into androidx-main
Jan 13, 2026
2c65621
Merge changes I95f209db,I154601ea into androidx-main
derekxu16 Jan 13, 2026
51e8909
Fix rotary over-scroll animation lag in snap behavior
clickxu Jan 7, 2026
28d1e5e
Merge "Allow new abstract methods to be added to sealed classes as lo…
fsladkey Jan 13, 2026
ed864f4
Merge "Change log tag of MR2ProviderServiceAdapter" into androidx-main
Jan 13, 2026
67a369f
Merge "Add PictureInPictureImpl for typically PiP usages" into androi…
hwwang-google Jan 13, 2026
f38947c
Merge "[TimePicker] Added sound and haptic feedback to time input err…
Jan 13, 2026
318491d
Merge "Fix rotary over-scroll animation lag in snap behavior" into an…
clickxu Jan 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.navigationevent.NavigationEventDispatcherOwner
import androidx.navigationevent.NavigationEventInfo
import androidx.navigationevent.compose.LocalNavigationEventDispatcherOwner

Expand Down Expand Up @@ -105,24 +106,26 @@ public object LocalOnBackPressedDispatcherOwner {
@OptIn(ExperimentalActivityApi::class)
@Composable
public fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
val navigationEventDispatcherOwner = LocalNavigationEventDispatcherOwner.current
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
// Short-circuit: Only read the legacy owner if the new one is missing.
val owner =
requireNotNull(navigationEventDispatcherOwner ?: onBackPressedDispatcherOwner) {
"No NavigationEventDispatcherOwner was provided via " +
"LocalNavigationEventDispatcherOwner and no OnBackPressedDispatcherOwner was " +
"provided via LocalOnBackPressedDispatcherOwner. Please provide one of the two."
LocalNavigationEventDispatcherOwner.current
?: LocalOnBackPressedDispatcherOwner.current
?: error(
"No NavigationEventDispatcherOwner was provided via " +
"LocalNavigationEventDispatcherOwner and no OnBackPressedDispatcherOwner was " +
"provided via LocalOnBackPressedDispatcherOwner. Please provide one of the two."
)

val dispatcher =
remember(owner) {
// Create a dispatcher compatibility layer that decides whether to use the new
// 'NavigationEventDispatcher' or the legacy 'OnBackPressedDispatcher'.
BackHandlerDispatcherCompat(
(owner as? NavigationEventDispatcherOwner)?.navigationEventDispatcher,
(owner as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher,
)
}

val dispatcher = remember {
// Create a dispatcher compatibility layer that decides whether to use the new
// 'NavigationEventDispatcher' or the legacy 'OnBackPressedDispatcher'.
BackHandlerDispatcherCompat(
navigationEventDispatcher = navigationEventDispatcherOwner?.navigationEventDispatcher,
onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher,
)
}

val compositeKey = currentCompositeKeyHashCode
val handler =
remember(dispatcher, compositeKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.activity.ActivityFlags
import androidx.activity.BackEventCompat
import androidx.activity.ExperimentalActivityApi
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.compose.internal.BackHandlerCompat
import androidx.activity.compose.internal.BackHandlerDispatcherCompat
import androidx.compose.runtime.Composable
Expand All @@ -32,6 +33,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.navigationevent.NavigationEventDispatcherOwner
import androidx.navigationevent.NavigationEventInfo
import androidx.navigationevent.compose.LocalNavigationEventDispatcherOwner
import java.util.concurrent.CancellationException
Expand Down Expand Up @@ -115,23 +117,25 @@ public fun PredictiveBackHandler(
suspend (progress: @JvmSuppressWildcards Flow<BackEventCompat>) -> @JvmSuppressWildcards
Unit,
) {
val navigationEventDispatcherOwner = LocalNavigationEventDispatcherOwner.current
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
// Short-circuit: Only read the legacy owner if the new one is missing.
val owner =
requireNotNull(navigationEventDispatcherOwner ?: onBackPressedDispatcherOwner) {
"No NavigationEventDispatcherOwner was provided via " +
"LocalNavigationEventDispatcherOwner and no OnBackPressedDispatcherOwner was " +
"provided via LocalOnBackPressedDispatcherOwner. Please provide one of the two."
}
LocalNavigationEventDispatcherOwner.current
?: LocalOnBackPressedDispatcherOwner.current
?: error(
"No NavigationEventDispatcherOwner was provided via " +
"LocalNavigationEventDispatcherOwner and no OnBackPressedDispatcherOwner was " +
"provided via LocalOnBackPressedDispatcherOwner. Please provide one of the two."
)

val dispatcher = remember {
// Create a dispatcher compatibility layer that decides whether to use the new
// 'NavigationEventDispatcher' or the legacy 'OnBackPressedDispatcher'.
BackHandlerDispatcherCompat(
navigationEventDispatcher = navigationEventDispatcherOwner?.navigationEventDispatcher,
onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher,
)
}
val dispatcher =
remember(owner) {
// Create a dispatcher compatibility layer that decides whether to use the new
// 'NavigationEventDispatcher' or the legacy 'OnBackPressedDispatcher'.
BackHandlerDispatcherCompat(
(owner as? NavigationEventDispatcherOwner)?.navigationEventDispatcher,
(owner as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher,
)
}

val scope = rememberCoroutineScope()
val compositeKey = currentCompositeKeyHashCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ class BinaryCompatibilityChecker(
return
}
when (this) {
is AbiClass -> isBinaryCompatibleWith(oldDeclaration as AbiClass, errors)
is AbiClass ->
DecoratedAbiClass(this, newLibraryDeclarations)
.isBinaryCompatibleWith(
DecoratedAbiClass(oldDeclaration as AbiClass, oldLibraryDeclarations),
errors,
)
is DecoratedAbiFunction ->
isBinaryCompatibleWith(oldDeclaration as DecoratedAbiFunction, errors)
is DecoratedAbiProperty ->
Expand All @@ -171,7 +176,10 @@ class BinaryCompatibilityChecker(
}
}

private fun AbiClass.isBinaryCompatibleWith(oldClass: AbiClass, errors: CompatibilityErrors) {
private fun DecoratedAbiClass.isBinaryCompatibleWith(
oldClass: DecoratedAbiClass,
errors: CompatibilityErrors,
) {
if (modality != oldClass.modality) {
when {
modality == AbiModality.OPEN && oldClass.modality == AbiModality.FINAL -> Unit
Expand Down Expand Up @@ -205,9 +213,9 @@ class BinaryCompatibilityChecker(
}

// Check that previous supertypes are still currently supertypes
allSuperTypes(newLibraryDeclarations)
allSuperTypes()
.isBinaryCompatibleWith(
oldClass.allSuperTypes(oldLibraryDeclarations),
oldClass.allSuperTypes(),
entityName = "superType",
uniqueId = AbiType::asString,
isBinaryCompatibleWith = AbiType::isBinaryCompatibleWith,
Expand All @@ -223,8 +231,8 @@ class BinaryCompatibilityChecker(
errors = errors,
isAllowedAddition = { false },
)
val newDecs = allDeclarationsIncludingInherited(newLibraryDeclarations)
val oldDecs = oldClass.allDeclarationsIncludingInherited(oldLibraryDeclarations)
val newDecs = allDeclarationsIncludingInherited()
val oldDecs = oldClass.allDeclarationsIncludingInherited()
newDecs.isBinaryCompatibleWith(
oldDecs,
entityName = "declaration",
Expand All @@ -233,8 +241,14 @@ class BinaryCompatibilityChecker(
isBinaryCompatibleWith(other, parentName, errs)
},
isAllowedAddition = {
when {
this is AbiFunction -> modality != AbiModality.ABSTRACT
when (this) {
is DecoratedAbiFunction,
is DecoratedAbiProperty -> (this as HasEffectiveModality).isSafeAddition()
is AbiFunction,
is AbiProperty ->
throw IllegalStateException(
"All functions / properties should be decorated"
)
else -> true
}
},
Expand All @@ -243,70 +257,6 @@ class BinaryCompatibilityChecker(
)
}

private fun AbiClass.allSuperTypes(declarations: Map<String, AbiDeclaration>): List<AbiType> {
return superTypes + superTypes.flatMap { it.allSuperTypes(declarations) }
}

private fun AbiType.allSuperTypes(declarations: Map<String, AbiDeclaration>): List<AbiType> {
val abiClass = declarations[asString()] as? AbiClass ?: return emptyList()
val superTypes = abiClass.superTypes
return superTypes + superTypes.flatMap { it.allSuperTypes(declarations) }
}

private fun AbiClass.allDeclarationsIncludingInherited(
oldLibraryDeclarations: Map<String, AbiDeclaration>
): List<AbiDeclaration> {
// Collect all the declarations directly on the class (without functions) +
// + all functions, (including inherited). The filterNot is to avoid listing
// functions directly on the class twice.
return declarations.filterNot { it is AbiFunction }.filterNot { it is AbiProperty } +
allMethodsIncludingInherited(oldLibraryDeclarations) +
allPropertiesIncludingInherited(oldLibraryDeclarations)
}

private fun AbiClass.allPropertiesIncludingInherited(
oldLibraryDeclarations: Map<String, AbiDeclaration>,
baseClass: AbiClass = this,
): List<DecoratedAbiProperty> {
val propertyMap =
declarations
.filterIsInstance<AbiProperty>()
.associate { it.asUnqualifiedTypeString() to DecoratedAbiProperty(it, baseClass) }
.toMutableMap()
superTypes
.map {
// we should throw here if we can't find the class in the package/dependencies
oldLibraryDeclarations[it.asString()]
}
.filterIsInstance<AbiClass>()
.flatMap { it.allPropertiesIncludingInherited(oldLibraryDeclarations, baseClass) }
.associateBy { it.asUnqualifiedTypeString() }
.forEach { (key, prop) -> propertyMap.putIfAbsent(key, prop) }
return propertyMap.values.toList()
}

private fun AbiClass.allMethodsIncludingInherited(
oldLibraryDeclarations: Map<String, AbiDeclaration>,
baseClass: AbiClass = this,
): List<DecoratedAbiFunction> {
val functionMap =
declarations
.filterIsInstance<AbiFunction>()
.associate { it.asUnqualifiedTypeString() to DecoratedAbiFunction(it, baseClass) }
.toMutableMap()
superTypes
.map {
oldLibraryDeclarations.getOrElse(it.className.toString()) {
throw IllegalStateException("Missing declaration ${it.asString()}")
}
}
.filterIsInstance<AbiClass>()
.flatMap { it.allMethodsIncludingInherited(oldLibraryDeclarations, baseClass) }
.associateBy { it.asUnqualifiedTypeString() }
.forEach { (key, func) -> functionMap.putIfAbsent(key, func) }
return functionMap.values.toList()
}

private fun DecoratedAbiFunction.isBinaryCompatibleWith(
otherFunction: DecoratedAbiFunction,
errors: CompatibilityErrors,
Expand Down Expand Up @@ -854,24 +804,120 @@ private fun File.asBaselineErrors(): Set<String> =
}
}

private class DecoratedAbiFunction(abiFunction: AbiFunction, val parentClass: AbiClass?) :
AbiFunction by abiFunction {
val effectiveModality
private interface HasEffectiveModality {
val effectiveModality: AbiModality

fun isSafeAddition(): Boolean
}

private class ClassMember(
private val parentClass: DecoratedAbiClass?,
private val modality: AbiModality,
) : HasEffectiveModality {
override val effectiveModality
get() =
when (parentClass?.modality) {
AbiModality.FINAL -> AbiModality.FINAL
else -> modality
}

override fun isSafeAddition(): Boolean {
if (parentClass?.modality == AbiModality.SEALED && !parentClass.hasAbstractSubClasses()) {
return true
}
return modality != AbiModality.ABSTRACT
}
}

private class DecoratedAbiProperty(abiProperty: AbiProperty, val parentClass: AbiClass?) :
AbiProperty by abiProperty {
val effectiveModality
get() =
when (parentClass?.modality) {
AbiModality.FINAL -> AbiModality.FINAL
else -> modality
private class DecoratedAbiFunction(abiFunction: AbiFunction, val parentClass: DecoratedAbiClass?) :
AbiFunction by abiFunction,
HasEffectiveModality by ClassMember(parentClass, abiFunction.modality)

private class DecoratedAbiProperty(abiProperty: AbiProperty, val parentClass: DecoratedAbiClass?) :
AbiProperty by abiProperty,
HasEffectiveModality by ClassMember(parentClass, abiProperty.modality)

private class DecoratedAbiClass(
abiClass: AbiClass,
private val allDeclarations: Map<String, AbiDeclaration>,
) : AbiClass by abiClass {

fun allSuperTypes(): List<AbiType> {
return superTypes + superTypes.flatMap { it.allSuperTypes(allDeclarations) }
}

fun subClasses(): List<AbiClass> {
return allDeclarations.values.filterIsInstance<AbiClass>().filter { abiClass ->
DecoratedAbiClass(abiClass, allDeclarations).allSuperTypes().any {
it.className == qualifiedName
}
}
}

fun hasAbstractSubClasses(): Boolean = subClasses().any { it.modality == AbiModality.ABSTRACT }

fun allDeclarationsIncludingInherited(): List<AbiDeclaration> {
// Collect all the declarations directly on the class (without functions / properties) +
// + all functions / properties (including inherited). The filterNot is to avoid listing
// functions / properties directly on the class twice.
return declarations.filterNot { it is AbiFunction || it is AbiProperty } +
allMethodsIncludingInherited() +
allPropertiesIncludingInherited()
}

fun allPropertiesIncludingInherited(baseClass: AbiClass = this): List<DecoratedAbiProperty> {
val propertyMap =
declarations
.filterIsInstance<AbiProperty>()
.associate {
it.asUnqualifiedTypeString() to
DecoratedAbiProperty(it, DecoratedAbiClass(baseClass, allDeclarations))
}
.toMutableMap()
superTypes
.asSequence()
.map {
allDeclarations.getOrElse(it.className.toString()) {
throw IllegalStateException("Missing declaration ${it.asString()}")
}
}
.filterIsInstance<AbiClass>()
.map { DecoratedAbiClass(it, allDeclarations) }
.flatMap { it.allPropertiesIncludingInherited(baseClass) }
.associateBy { it.asUnqualifiedTypeString() }
.forEach { (key, prop) -> propertyMap.putIfAbsent(key, prop) }
return propertyMap.values.toList()
}

fun allMethodsIncludingInherited(baseClass: AbiClass = this): List<DecoratedAbiFunction> {
val functionMap =
declarations
.filterIsInstance<AbiFunction>()
.associate {
it.asUnqualifiedTypeString() to
DecoratedAbiFunction(it, DecoratedAbiClass(baseClass, allDeclarations))
}
.toMutableMap()
superTypes
.asSequence()
.map {
allDeclarations.getOrElse(it.className.toString()) {
throw IllegalStateException("Missing declaration ${it.asString()}")
}
}
.filterIsInstance<AbiClass>()
.map { DecoratedAbiClass(it, allDeclarations) }
.flatMap { it.allMethodsIncludingInherited(baseClass) }
.associateBy { it.asUnqualifiedTypeString() }
.forEach { (key, func) -> functionMap.putIfAbsent(key, func) }
return functionMap.values.toList()
}

private fun AbiType.allSuperTypes(declarations: Map<String, AbiDeclaration>): List<AbiType> {
val abiClass = declarations[asString()] as? AbiClass ?: return emptyList()
val superTypes = abiClass.superTypes
return superTypes + superTypes.flatMap { it.allSuperTypes(declarations) }
}
}

private class DecoratedAbiValueParameter(val index: Int, param: AbiValueParameter) :
Expand Down
Loading
Loading